From 1e8b3ac7dfb355e7f372e81a9128dfa26ecfdb0d Mon Sep 17 00:00:00 2001 From: krahets Date: Tue, 31 Mar 2026 05:33:54 +0800 Subject: [PATCH] deploy --- assets/javascripts/bundle.c2b142ea.min.js | 2 +- en/assets/javascripts/bundle.c2b142ea.min.js | 2 +- en/chapter_stack_and_queue/queue/index.html | 2 +- en/javascripts/katex.js | 2 +- en/javascripts/mathjax.js | 2 +- en/javascripts/starfield.js | 2 +- en/search.json | 2 +- en/stylesheets/extra.css | 680 ++++++++---------- ja/assets/covers/chapter_appendix.jpg | Bin 134704 -> 135805 bytes .../covers/chapter_array_and_linkedlist.jpg | Bin 128263 -> 128843 bytes ja/assets/covers/chapter_backtracking.jpg | Bin 131656 -> 130797 bytes .../covers/chapter_complexity_analysis.jpg | Bin 97505 -> 97585 bytes ja/assets/covers/chapter_data_structure.jpg | Bin 144041 -> 143901 bytes .../covers/chapter_divide_and_conquer.jpg | Bin 106324 -> 106671 bytes .../covers/chapter_dynamic_programming.jpg | Bin 172922 -> 168757 bytes ja/assets/covers/chapter_graph.jpg | Bin 82583 -> 82655 bytes ja/assets/covers/chapter_greedy.jpg | Bin 132061 -> 131394 bytes ja/assets/covers/chapter_hashing.jpg | Bin 131578 -> 133166 bytes ja/assets/covers/chapter_heap.jpg | Bin 112604 -> 112458 bytes ja/assets/covers/chapter_introduction.jpg | Bin 139006 -> 138505 bytes ja/assets/covers/chapter_preface.jpg | Bin 113913 -> 114340 bytes ja/assets/covers/chapter_searching.jpg | Bin 220729 -> 219531 bytes ja/assets/covers/chapter_sorting.jpg | Bin 92214 -> 92389 bytes ja/assets/covers/chapter_stack_and_queue.jpg | Bin 112044 -> 112247 bytes ja/assets/covers/chapter_tree.jpg | Bin 119581 -> 118872 bytes ja/assets/javascripts/bundle.c2b142ea.min.js | 2 +- ja/chapter_stack_and_queue/queue/index.html | 2 +- ja/javascripts/katex.js | 2 +- ja/javascripts/mathjax.js | 2 +- ja/javascripts/starfield.js | 2 +- ja/search.json | 2 +- ja/stylesheets/extra.css | 680 ++++++++---------- javascripts/katex.js | 2 +- javascripts/mathjax.js | 2 +- javascripts/starfield.js | 2 +- ru/404.html | 58 +- ru/assets/covers/chapter_appendix.jpg | Bin 135651 -> 135560 bytes .../covers/chapter_array_and_linkedlist.jpg | Bin 128537 -> 127252 bytes ru/assets/covers/chapter_backtracking.jpg | Bin 132554 -> 133089 bytes .../covers/chapter_complexity_analysis.jpg | Bin 96935 -> 96059 bytes ru/assets/covers/chapter_data_structure.jpg | Bin 144365 -> 144513 bytes .../covers/chapter_dynamic_programming.jpg | Bin 174477 -> 174724 bytes ru/assets/covers/chapter_graph.jpg | Bin 82689 -> 83148 bytes ru/assets/covers/chapter_hashing.jpg | Bin 133919 -> 132194 bytes ru/assets/covers/chapter_hello_algo.jpg | Bin 147884 -> 150238 bytes ru/assets/covers/chapter_introduction.jpg | Bin 139471 -> 138897 bytes ru/assets/covers/chapter_preface.jpg | Bin 116035 -> 115376 bytes ru/assets/covers/chapter_sorting.jpg | Bin 95364 -> 96080 bytes ru/assets/covers/chapter_stack_and_queue.jpg | Bin 113383 -> 113171 bytes ru/assets/covers/chapter_tree.jpg | Bin 120467 -> 120948 bytes ru/assets/javascripts/bundle.c2b142ea.min.js | 2 +- ru/chapter_appendix/contribution/index.html | 58 +- ru/chapter_appendix/index.html | 58 +- ru/chapter_appendix/installation/index.html | 58 +- ru/chapter_appendix/terminology/index.html | 58 +- .../array/index.html | 58 +- ru/chapter_array_and_linkedlist/index.html | 60 +- .../linked_list/index.html | 58 +- .../list/index.html | 62 +- .../ram_and_cache/index.html | 60 +- .../summary/index.html | 62 +- .../backtracking_algorithm/index.html | 58 +- ru/chapter_backtracking/index.html | 60 +- .../n_queens_problem/index.html | 60 +- .../permutations_problem/index.html | 58 +- .../subset_sum_problem/index.html | 62 +- ru/chapter_backtracking/summary/index.html | 62 +- .../index.html | 58 +- .../iteration_and_recursion/index.html | 58 +- .../performance_evaluation/index.html | 58 +- .../space_complexity/index.html | 58 +- .../summary/index.html | 58 +- .../time_complexity/index.html | 58 +- .../basic_data_types/index.html | 58 +- .../character_encoding/index.html | 58 +- .../index.html | 58 +- ru/chapter_data_structure/index.html | 58 +- .../number_encoding/index.html | 58 +- ru/chapter_data_structure/summary/index.html | 58 +- .../binary_search_recur/index.html | 70 +- .../build_binary_tree_problem/index.html | 62 +- .../divide_and_conquer/index.html | 70 +- .../hanota_problem/index.html | 58 +- ru/chapter_divide_and_conquer/index.html | 66 +- .../summary/index.html | 58 +- .../dp_problem_features/index.html | 62 +- .../dp_solution_pipeline/index.html | 58 +- .../edit_distance_problem/index.html | 62 +- ru/chapter_dynamic_programming/index.html | 66 +- .../intro_to_dynamic_programming/index.html | 60 +- .../knapsack_problem/index.html | 62 +- .../summary/index.html | 58 +- .../unbounded_knapsack_problem/index.html | 60 +- ru/chapter_graph/graph/index.html | 62 +- ru/chapter_graph/graph_operations/index.html | 60 +- ru/chapter_graph/graph_traversal/index.html | 66 +- ru/chapter_graph/index.html | 62 +- ru/chapter_graph/summary/index.html | 60 +- .../fractional_knapsack_problem/index.html | 58 +- ru/chapter_greedy/greedy_algorithm/index.html | 58 +- ru/chapter_greedy/index.html | 58 +- .../max_capacity_problem/index.html | 58 +- .../max_product_cutting_problem/index.html | 58 +- ru/chapter_greedy/summary/index.html | 58 +- ru/chapter_hashing/hash_algorithm/index.html | 60 +- ru/chapter_hashing/hash_collision/index.html | 62 +- ru/chapter_hashing/hash_map/index.html | 58 +- ru/chapter_hashing/index.html | 60 +- ru/chapter_hashing/summary/index.html | 62 +- ru/chapter_heap/build_heap/index.html | 62 +- ru/chapter_heap/heap/index.html | 58 +- ru/chapter_heap/index.html | 64 +- ru/chapter_heap/summary/index.html | 62 +- ru/chapter_heap/top_k/index.html | 60 +- ru/chapter_hello_algo/index.html | 58 +- .../algorithms_are_everywhere/index.html | 62 +- ru/chapter_introduction/index.html | 60 +- ru/chapter_introduction/summary/index.html | 62 +- .../what_is_dsa/index.html | 60 +- ru/chapter_preface/about_the_book/index.html | 58 +- ru/chapter_preface/index.html | 58 +- ru/chapter_preface/suggestions/index.html | 58 +- ru/chapter_preface/summary/index.html | 58 +- ru/chapter_reference/index.html | 58 +- ru/chapter_searching/binary_search/index.html | 62 +- .../binary_search_edge/index.html | 68 +- .../binary_search_insertion/index.html | 64 +- ru/chapter_searching/index.html | 70 +- .../replace_linear_by_hashing/index.html | 68 +- .../searching_algorithm_revisited/index.html | 64 +- ru/chapter_searching/summary/index.html | 62 +- ru/chapter_sorting/bubble_sort/index.html | 64 +- ru/chapter_sorting/bucket_sort/index.html | 58 +- ru/chapter_sorting/counting_sort/index.html | 58 +- ru/chapter_sorting/heap_sort/index.html | 58 +- ru/chapter_sorting/index.html | 62 +- ru/chapter_sorting/insertion_sort/index.html | 64 +- ru/chapter_sorting/merge_sort/index.html | 58 +- ru/chapter_sorting/quick_sort/index.html | 62 +- ru/chapter_sorting/radix_sort/index.html | 58 +- ru/chapter_sorting/selection_sort/index.html | 62 +- .../sorting_algorithm/index.html | 58 +- ru/chapter_sorting/summary/index.html | 58 +- ru/chapter_stack_and_queue/deque/index.html | 58 +- ru/chapter_stack_and_queue/index.html | 58 +- ru/chapter_stack_and_queue/queue/index.html | 60 +- ru/chapter_stack_and_queue/stack/index.html | 58 +- ru/chapter_stack_and_queue/summary/index.html | 58 +- .../array_representation_of_tree/index.html | 60 +- ru/chapter_tree/avl_tree/index.html | 62 +- ru/chapter_tree/binary_search_tree/index.html | 62 +- ru/chapter_tree/binary_tree/index.html | 58 +- .../binary_tree_traversal/index.html | 62 +- ru/chapter_tree/index.html | 62 +- ru/chapter_tree/summary/index.html | 60 +- ru/index.html | 70 +- ru/javascripts/katex.js | 2 +- ru/javascripts/mathjax.js | 2 +- ru/javascripts/starfield.js | 2 +- ru/search.json | 2 +- ru/stylesheets/extra.css | 680 ++++++++---------- stylesheets/extra.css | 680 ++++++++---------- .../assets/javascripts/bundle.c2b142ea.min.js | 2 +- .../chapter_stack_and_queue/queue/index.html | 2 +- zh-hant/javascripts/katex.js | 2 +- zh-hant/javascripts/mathjax.js | 2 +- zh-hant/javascripts/starfield.js | 2 +- zh-hant/search.json | 2 +- zh-hant/stylesheets/extra.css | 680 ++++++++---------- 169 files changed, 4684 insertions(+), 5172 deletions(-) diff --git a/assets/javascripts/bundle.c2b142ea.min.js b/assets/javascripts/bundle.c2b142ea.min.js index 36c87fe94..3a9ab3cd4 100644 --- a/assets/javascripts/bundle.c2b142ea.min.js +++ b/assets/javascripts/bundle.c2b142ea.min.js @@ -1,4 +1,4 @@ "use strict";(()=>{var xc=Object.create;var kn=Object.defineProperty,wc=Object.defineProperties,Ec=Object.getOwnPropertyDescriptor,Tc=Object.getOwnPropertyDescriptors,Sc=Object.getOwnPropertyNames,Dr=Object.getOwnPropertySymbols,Oc=Object.getPrototypeOf,An=Object.prototype.hasOwnProperty,Fo=Object.prototype.propertyIsEnumerable;var jo=(e,t,r)=>t in e?kn(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,H=(e,t)=>{for(var r in t||(t={}))An.call(t,r)&&jo(e,r,t[r]);if(Dr)for(var r of Dr(t))Fo.call(t,r)&&jo(e,r,t[r]);return e},He=(e,t)=>wc(e,Tc(t));var gr=(e,t)=>{var r={};for(var n in e)An.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&Dr)for(var n of Dr(e))t.indexOf(n)<0&&Fo.call(e,n)&&(r[n]=e[n]);return r};var Cn=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Lc=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Sc(t))!An.call(e,o)&&o!==r&&kn(e,o,{get:()=>t[o],enumerable:!(n=Ec(t,o))||n.enumerable});return e};var _r=(e,t,r)=>(r=e!=null?xc(Oc(e)):{},Lc(t||!e||!e.__esModule?kn(r,"default",{value:e,enumerable:!0}):r,e));var Uo=(e,t,r)=>new Promise((n,o)=>{var i=c=>{try{s(r.next(c))}catch(l){o(l)}},a=c=>{try{s(r.throw(c))}catch(l){o(l)}},s=c=>c.done?n(c.value):Promise.resolve(c.value).then(i,a);s((r=r.apply(e,t)).next())});var Do=Cn((Hn,No)=>{(function(e,t){typeof Hn=="object"&&typeof No!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Hn,(function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(_){return!!(_&&_!==document&&_.nodeName!=="HTML"&&_.nodeName!=="BODY"&&"classList"in _&&"contains"in _.classList)}function c(_){var de=_.type,be=_.tagName;return!!(be==="INPUT"&&a[de]&&!_.readOnly||be==="TEXTAREA"&&!_.readOnly||_.isContentEditable)}function l(_){_.classList.contains("focus-visible")||(_.classList.add("focus-visible"),_.setAttribute("data-focus-visible-added",""))}function u(_){_.hasAttribute("data-focus-visible-added")&&(_.classList.remove("focus-visible"),_.removeAttribute("data-focus-visible-added"))}function p(_){_.metaKey||_.altKey||_.ctrlKey||(s(r.activeElement)&&l(r.activeElement),n=!0)}function d(_){n=!1}function m(_){s(_.target)&&(n||c(_.target))&&l(_.target)}function h(_){s(_.target)&&(_.target.classList.contains("focus-visible")||_.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(_.target))}function v(_){document.visibilityState==="hidden"&&(o&&(n=!0),x())}function x(){document.addEventListener("mousemove",E),document.addEventListener("mousedown",E),document.addEventListener("mouseup",E),document.addEventListener("pointermove",E),document.addEventListener("pointerdown",E),document.addEventListener("pointerup",E),document.addEventListener("touchmove",E),document.addEventListener("touchstart",E),document.addEventListener("touchend",E)}function w(){document.removeEventListener("mousemove",E),document.removeEventListener("mousedown",E),document.removeEventListener("mouseup",E),document.removeEventListener("pointermove",E),document.removeEventListener("pointerdown",E),document.removeEventListener("pointerup",E),document.removeEventListener("touchmove",E),document.removeEventListener("touchstart",E),document.removeEventListener("touchend",E)}function E(_){_.target.nodeName&&_.target.nodeName.toLowerCase()==="html"||(n=!1,w())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",d,!0),document.addEventListener("pointerdown",d,!0),document.addEventListener("touchstart",d,!0),document.addEventListener("visibilitychange",v,!0),x(),r.addEventListener("focus",m,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)}))});var So=Cn((M0,vs)=>{"use strict";var Gu=/["'&<>]/;vs.exports=Ju;function Ju(e){var t=""+e,r=Gu.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i{(function(t,r){typeof jr=="object"&&typeof Lo=="object"?Lo.exports=r():typeof define=="function"&&define.amd?define([],r):typeof jr=="object"?jr.ClipboardJS=r():t.ClipboardJS=r()})(jr,function(){return(function(){var e={686:(function(n,o,i){"use strict";i.d(o,{default:function(){return vr}});var a=i(279),s=i.n(a),c=i(370),l=i.n(c),u=i(817),p=i.n(u);function d(B){try{return document.execCommand(B)}catch(C){return!1}}var m=function(C){var k=p()(C);return d("cut"),k},h=m;function v(B){var C=document.documentElement.getAttribute("dir")==="rtl",k=document.createElement("textarea");k.style.fontSize="12pt",k.style.border="0",k.style.padding="0",k.style.margin="0",k.style.position="absolute",k.style[C?"right":"left"]="-9999px";var D=window.pageYOffset||document.documentElement.scrollTop;return k.style.top="".concat(D,"px"),k.setAttribute("readonly",""),k.value=B,k}var x=function(C,k){var D=v(C);k.container.appendChild(D);var W=p()(D);return d("copy"),D.remove(),W},w=function(C){var k=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},D="";return typeof C=="string"?D=x(C,k):C instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(C==null?void 0:C.type)?D=x(C.value,k):(D=p()(C),d("copy")),D},E=w;function _(B){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?_=function(k){return typeof k}:_=function(k){return k&&typeof Symbol=="function"&&k.constructor===Symbol&&k!==Symbol.prototype?"symbol":typeof k},_(B)}var de=function(){var C=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},k=C.action,D=k===void 0?"copy":k,W=C.container,Z=C.target,We=C.text;if(D!=="copy"&&D!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Z!==void 0)if(Z&&_(Z)==="object"&&Z.nodeType===1){if(D==="copy"&&Z.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(D==="cut"&&(Z.hasAttribute("readonly")||Z.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(We)return E(We,{container:W});if(Z)return D==="cut"?h(Z):E(Z,{container:W})},be=de;function M(B){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?M=function(k){return typeof k}:M=function(k){return k&&typeof Symbol=="function"&&k.constructor===Symbol&&k!==Symbol.prototype?"symbol":typeof k},M(B)}function O(B,C){if(!(B instanceof C))throw new TypeError("Cannot call a class as a function")}function N(B,C){for(var k=0;k0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof W.action=="function"?W.action:this.defaultAction,this.target=typeof W.target=="function"?W.target:this.defaultTarget,this.text=typeof W.text=="function"?W.text:this.defaultText,this.container=M(W.container)==="object"?W.container:document.body}},{key:"listenClick",value:function(W){var Z=this;this.listener=l()(W,"click",function(We){return Z.onClick(We)})}},{key:"onClick",value:function(W){var Z=W.delegateTarget||W.currentTarget,We=this.action(Z)||"copy",Gt=be({action:We,container:this.container,target:this.target(Z),text:this.text(Z)});this.emit(Gt?"success":"error",{action:We,text:Gt,trigger:Z,clearSelection:function(){Z&&Z.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(W){return Yt("action",W)}},{key:"defaultTarget",value:function(W){var Z=Yt("target",W);if(Z)return document.querySelector(Z)}},{key:"defaultText",value:function(W){return Yt("text",W)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(W){var Z=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return E(W,Z)}},{key:"cut",value:function(W){return h(W)}},{key:"isSupported",value:function(){var W=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Z=typeof W=="string"?[W]:W,We=!!document.queryCommandSupported;return Z.forEach(function(Gt){We=We&&!!document.queryCommandSupported(Gt)}),We}}]),k})(s()),vr=Mt}),828:(function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,c){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(c))return s;s=s.parentNode}}n.exports=a}),438:(function(n,o,i){var a=i(828);function s(u,p,d,m,h){var v=l.apply(this,arguments);return u.addEventListener(d,v,h),{destroy:function(){u.removeEventListener(d,v,h)}}}function c(u,p,d,m,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof d=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return s(v,p,d,m,h)}))}function l(u,p,d,m){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&m.call(u,h)}}n.exports=c}),879:(function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}}),370:(function(n,o,i){var a=i(879),s=i(438);function c(d,m,h){if(!d&&!m&&!h)throw new Error("Missing required arguments");if(!a.string(m))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(d))return l(d,m,h);if(a.nodeList(d))return u(d,m,h);if(a.string(d))return p(d,m,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function l(d,m,h){return d.addEventListener(m,h),{destroy:function(){d.removeEventListener(m,h)}}}function u(d,m,h){return Array.prototype.forEach.call(d,function(v){v.addEventListener(m,h)}),{destroy:function(){Array.prototype.forEach.call(d,function(v){v.removeEventListener(m,h)})}}}function p(d,m,h){return s(document.body,d,m,h)}n.exports=c}),817:(function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),l=document.createRange();l.selectNodeContents(i),c.removeAllRanges(),c.addRange(l),a=c.toString()}return a}n.exports=o}),279:(function(n){function o(){}o.prototype={on:function(i,a,s){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var c=this;function l(){c.off(i,l),a.apply(s,arguments)}return l._=a,this.on(i,l,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),c=0,l=s.length;for(c;c0&&i[i.length-1])&&(l[0]===6||l[0]===2)){r=0;continue}if(l[0]===3&&(!i||l[1]>i[0]&&l[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function te(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(s){a={error:s}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function ne(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||c(m,v)})},h&&(o[m]=h(o[m])))}function c(m,h){try{l(n[m](h))}catch(v){d(i[0][3],v)}}function l(m){m.value instanceof kt?Promise.resolve(m.value.v).then(u,p):d(i[0][2],m)}function u(m){c("next",m)}function p(m){c("throw",m)}function d(m,h){m(h),i.shift(),i.length&&c(i[0][0],i[0][1])}}function zo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof $e=="function"?$e(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(s,c){a=e[i](a),o(s,c,a.done,a.value)})}}function o(i,a,s,c){Promise.resolve(c).then(function(l){i({value:l,done:s})},a)}}function F(e){return typeof e=="function"}function Jt(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Vr=Jt(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: `+r.map(function(n,o){return o+1+") "+n.toString()}).join(` `):"",this.name="UnsubscriptionError",this.errors=r}});function ct(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var rt=(function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=$e(a),c=s.next();!c.done;c=s.next()){var l=c.value;l.remove(this)}}catch(v){t={error:v}}finally{try{c&&!c.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var u=this.initialTeardown;if(F(u))try{u()}catch(v){i=v instanceof Vr?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var d=$e(p),m=d.next();!m.done;m=d.next()){var h=m.value;try{qo(h)}catch(v){i=i!=null?i:[],v instanceof Vr?i=ne(ne([],te(i)),te(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{m&&!m.done&&(o=d.return)&&o.call(d)}finally{if(n)throw n.error}}}if(i)throw new Vr(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)qo(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&ct(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&ct(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=(function(){var t=new e;return t.closed=!0,t})(),e})();var Pn=rt.EMPTY;function zr(e){return e instanceof rt||e&&"closed"in e&&F(e.remove)&&F(e.add)&&F(e.unsubscribe)}function qo(e){F(e)?e():e.unsubscribe()}var Je={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var Xt={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Pn:(this.currentObservers=null,s.push(r),new rt(function(){n.currentObservers=null,ct(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new U;return r.source=this,r},t.create=function(r,n){return new Qo(r,n)},t})(U);var Qo=(function(e){ue(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Pn},t})(I);var Un=(function(e){ue(t,e);function t(r){var n=e.call(this)||this;return n._value=r,n}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var n=e.prototype._subscribe.call(this,r);return!n.closed&&r.next(this._value),n},t.prototype.getValue=function(){var r=this,n=r.hasError,o=r.thrownError,i=r._value;if(n)throw o;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t})(I);var xr={now:function(){return(xr.delegate||Date).now()},delegate:void 0};var wr=(function(e){ue(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=xr);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,c=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),c=0;c0?e.prototype.schedule.call(this,r,n):(this.delay=n,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,n){return n>0||this.closed?e.prototype.execute.call(this,r,n):this._execute(r,n)},t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!=null&&o>0||o==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.flush(this),0)},t})(tr);var ri=(function(e){ue(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t})(rr);var Wn=new ri(ti);var ni=(function(e){ue(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o}return t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!==null&&o>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=er.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var a=r.actions;n!=null&&n===r._scheduled&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==n&&(er.cancelAnimationFrame(n),r._scheduled=void 0)},t})(tr);var oi=(function(e){ue(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n;r?n=r.id:(n=this._scheduled,this._scheduled=void 0);var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t})(rr);var je=new oi(ni);var y=new U(function(e){return e.complete()});function Br(e){return e&&F(e.schedule)}function Vn(e){return e[e.length-1]}function _t(e){return F(Vn(e))?e.pop():void 0}function qe(e){return Br(Vn(e))?e.pop():void 0}function Yr(e,t){return typeof Vn(e)=="number"?e.pop():t}var nr=(function(e){return e&&typeof e.length=="number"&&typeof e!="function"});function Gr(e){return F(e==null?void 0:e.then)}function Jr(e){return F(e[Qt])}function Xr(e){return Symbol.asyncIterator&&F(e==null?void 0:e[Symbol.asyncIterator])}function Zr(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Rc(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qr=Rc();function en(e){return F(e==null?void 0:e[Qr])}function tn(e){return Vo(this,arguments,function(){var r,n,o,i;return Wr(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,kt(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,kt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,kt(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function rn(e){return F(e==null?void 0:e.getReader)}function q(e){if(e instanceof U)return e;if(e!=null){if(Jr(e))return jc(e);if(nr(e))return Fc(e);if(Gr(e))return Uc(e);if(Xr(e))return ii(e);if(en(e))return Nc(e);if(rn(e))return Dc(e)}throw Zr(e)}function jc(e){return new U(function(t){var r=e[Qt]();if(F(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Fc(e){return new U(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?L(function(o,i){return e(o,i,n)}):Oe,Me(1),r?ot(t):wi(function(){return new on}))}}function Gn(e){return e<=0?function(){return y}:S(function(t,r){var n=[];t.subscribe(T(r,function(o){n.push(o),e=2,!0))}function xe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new I}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,c=s===void 0?!0:s;return function(l){var u,p,d,m=0,h=!1,v=!1,x=function(){p==null||p.unsubscribe(),p=void 0},w=function(){x(),u=d=void 0,h=v=!1},E=function(){var _=u;w(),_==null||_.unsubscribe()};return S(function(_,de){m++,!v&&!h&&x();var be=d=d!=null?d:r();de.add(function(){m--,m===0&&!v&&!h&&(p=Jn(E,c))}),be.subscribe(de),!u&&m>0&&(u=new Ct({next:function(M){return be.next(M)},error:function(M){v=!0,x(),p=Jn(w,o,M),be.error(M)},complete:function(){h=!0,x(),p=Jn(w,a),be.complete()}}),q(_).subscribe(u))})(l)}}function Jn(e,t){for(var r=[],n=2;ne.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function G(e,t=document){let r=Le(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function Le(e,t=document){return t.querySelector(e)||void 0}function xt(){var e,t,r,n;return(n=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?n:void 0}var il=R(b(document.body,"focusin"),b(document.body,"focusout")).pipe(Be(1),J(void 0),f(()=>xt()||document.body),se(1));function ir(e){return il.pipe(f(t=>e.contains(t)),ie())}function Ft(e,t){let{matches:r}=matchMedia("(hover)");return j(()=>(r?R(b(e,"mouseenter").pipe(f(()=>!0)),b(e,"mouseleave").pipe(f(()=>!1))):R(b(e,"touchstart").pipe(f(()=>!0)),b(e,"touchend").pipe(f(()=>!1)),b(e,"touchcancel").pipe(f(()=>!1)))).pipe(t?Tr(o=>Ve(+!o*t)):Oe,J(!0,e.matches(":hover"))))}function Oi(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Oi(e,r)}function A(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)Oi(n,o);return n}function Li(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function ar(e){let t=A("script",{src:e});return j(()=>(document.head.appendChild(t),R(b(t,"load"),b(t,"error").pipe(g(()=>zn(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(f(()=>{}),V(()=>document.head.removeChild(t)),Me(1))))}var Mi=new I,al=j(()=>typeof ResizeObserver=="undefined"?ar("https://unpkg.com/resize-observer-polyfill"):Y(void 0)).pipe(f(()=>new ResizeObserver(e=>e.forEach(t=>Mi.next(t)))),g(e=>R(Ke,Y(e)).pipe(V(()=>e.disconnect()))),se(1));function Ae(e){return{width:e.offsetWidth,height:e.offsetHeight}}function Re(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return al.pipe($(r=>r.observe(t)),g(r=>Mi.pipe(L(n=>n.target===t),V(()=>r.unobserve(t)))),f(()=>Ae(e)),J(Ae(e)))}function Mr(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ki(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Ai(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function wt(e){return{x:e.offsetLeft,y:e.offsetTop}}function Ci(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function Hi(e){return R(b(window,"load"),b(window,"resize")).pipe(Xe(0,je),f(()=>wt(e)),J(wt(e)))}function ln(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ut(e){return R(b(e,"scroll"),b(window,"scroll"),b(window,"resize")).pipe(Xe(0,je),f(()=>ln(e)),J(ln(e)))}var $i=new I,sl=j(()=>Y(new IntersectionObserver(e=>{for(let t of e)$i.next(t)},{threshold:0}))).pipe(g(e=>R(Ke,Y(e)).pipe(V(()=>e.disconnect()))),se(1));function Et(e){return sl.pipe($(t=>t.observe(e)),g(t=>$i.pipe(L(({target:r})=>r===e),V(()=>t.unobserve(e)),f(({isIntersecting:r})=>r))))}var cl=Object.create,la=Object.defineProperty,ll=Object.getOwnPropertyDescriptor,ul=Object.getOwnPropertyNames,pl=Object.getPrototypeOf,fl=Object.prototype.hasOwnProperty,ml=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),dl=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ul(t))!fl.call(e,o)&&o!==r&&la(e,o,{get:()=>t[o],enumerable:!(n=ll(t,o))||n.enumerable});return e},hl=(e,t,r)=>(r=e!=null?cl(pl(e)):{},dl(t||!e||!e.__esModule?la(r,"default",{value:e,enumerable:!0}):r,e)),vl=ml((e,t)=>{var r="Expected a function",n=NaN,o="[object Symbol]",i=/^\s+|\s+$/g,a=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,c=/^0o[0-7]+$/i,l=parseInt,u=typeof global=="object"&&global&&global.Object===Object&&global,p=typeof self=="object"&&self&&self.Object===Object&&self,d=u||p||Function("return this")(),m=Object.prototype,h=m.toString,v=Math.max,x=Math.min,w=function(){return d.Date.now()};function E(O,N,ee){var le,ce,Ne,bt,De,st,tt=0,Yt=!1,Mt=!1,vr=!0;if(typeof O!="function")throw new TypeError(r);N=M(N)||0,_(ee)&&(Yt=!!ee.leading,Mt="maxWait"in ee,Ne=Mt?v(M(ee.maxWait)||0,N):Ne,vr="trailing"in ee?!!ee.trailing:vr);function B(Te){var gt=le,br=ce;return le=ce=void 0,tt=Te,bt=O.apply(br,gt),bt}function C(Te){return tt=Te,De=setTimeout(W,N),Yt?B(Te):bt}function k(Te){var gt=Te-st,br=Te-tt,Ro=N-gt;return Mt?x(Ro,Ne-br):Ro}function D(Te){var gt=Te-st,br=Te-tt;return st===void 0||gt>=N||gt<0||Mt&&br>=Ne}function W(){var Te=w();if(D(Te))return Z(Te);De=setTimeout(W,k(Te))}function Z(Te){return De=void 0,vr&&le?B(Te):(le=ce=void 0,bt)}function We(){De!==void 0&&clearTimeout(De),tt=0,le=st=ce=De=void 0}function Gt(){return De===void 0?bt:Z(w())}function Nr(){var Te=w(),gt=D(Te);if(le=arguments,ce=this,st=Te,gt){if(De===void 0)return C(st);if(Mt)return De=setTimeout(W,N),B(st)}return De===void 0&&(De=setTimeout(W,N)),bt}return Nr.cancel=We,Nr.flush=Gt,Nr}function _(O){var N=typeof O;return!!O&&(N=="object"||N=="function")}function de(O){return!!O&&typeof O=="object"}function be(O){return typeof O=="symbol"||de(O)&&h.call(O)==o}function M(O){if(typeof O=="number")return O;if(be(O))return n;if(_(O)){var N=typeof O.valueOf=="function"?O.valueOf():O;O=_(N)?N+"":N}if(typeof O!="string")return O===0?O:+O;O=O.replace(i,"");var ee=s.test(O);return ee||c.test(O)?l(O.slice(2),ee?2:8):a.test(O)?n:+O}t.exports=E}),yn,K,ua,pa,Nt,Pi,fa,ma,da,lo,to,ro,bl,Ar={},ha=[],gl=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Pr=Array.isArray;function pt(e,t){for(var r in t)e[r]=t[r];return e}function uo(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function Wt(e,t,r){var n,o,i,a={};for(i in t)i=="key"?n=t[i]:i=="ref"?o=t[i]:a[i]=t[i];if(arguments.length>2&&(a.children=arguments.length>3?yn.call(arguments,2):r),typeof e=="function"&&e.defaultProps!=null)for(i in e.defaultProps)a[i]===void 0&&(a[i]=e.defaultProps[i]);return fn(e,a,n,o,null)}function fn(e,t,r,n,o){var i={type:e,props:t,key:r,ref:n,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o!=null?o:++ua,__i:-1,__u:0};return o==null&&K.vnode!=null&&K.vnode(i),i}function ft(e){return e.children}function at(e,t){this.props=e,this.context=t}function cr(e,t){if(t==null)return e.__?cr(e.__,e.__i+1):null;for(var r;ts&&Nt.sort(ma),e=Nt.shift(),s=Nt.length,e.__d&&(r=void 0,n=void 0,o=(n=(t=e).__v).__e,i=[],a=[],t.__P&&((r=pt({},n)).__v=n.__v+1,K.vnode&&K.vnode(r),po(t.__P,r,n,t.__n,t.__P.namespaceURI,32&n.__u?[o]:null,i,o!=null?o:cr(n),!!(32&n.__u),a),r.__v=n.__v,r.__.__k[r.__i]=r,_a(i,r,a),n.__e=n.__=null,r.__e!=o&&va(r)));vn.__r=0}function ba(e,t,r,n,o,i,a,s,c,l,u){var p,d,m,h,v,x,w,E=n&&n.__k||ha,_=t.length;for(c=_l(r,t,E,c,_),p=0;p<_;p++)(m=r.__k[p])!=null&&(d=m.__i==-1?Ar:E[m.__i]||Ar,m.__i=p,x=po(e,m,d,o,i,a,s,c,l,u),h=m.__e,m.ref&&d.ref!=m.ref&&(d.ref&&fo(d.ref,null,m),u.push(m.ref,m.__c||h,m)),v==null&&h!=null&&(v=h),(w=!!(4&m.__u))||d.__k===m.__k?c=ga(m,c,e,w):typeof m.type=="function"&&x!==void 0?c=x:h&&(c=h.nextSibling),m.__u&=-7);return r.__e=v,c}function _l(e,t,r,n,o){var i,a,s,c,l,u=r.length,p=u,d=0;for(e.__k=new Array(o),i=0;i0?fn(a.type,a.props,a.key,a.ref?a.ref:null,a.__v):a).__=e,a.__b=e.__b+1,s=null,(l=a.__i=yl(a,r,c,p))!=-1&&(p--,(s=r[l])&&(s.__u|=2)),s==null||s.__v==null?(l==-1&&(o>u?d--:oc?d--:d++,a.__u|=4))):e.__k[i]=null;if(p)for(i=0;i(u?1:0)){for(o=r-1,i=r+1;o>=0||i=0?o--:i++])!=null&&!(2&l.__u)&&s==l.key&&c==l.type)return a}return-1}function Ri(e,t,r){t[0]=="-"?e.setProperty(t,r!=null?r:""):e[t]=r==null?"":typeof r!="number"||gl.test(t)?r:r+"px"}function un(e,t,r,n,o){var i,a;e:if(t=="style")if(typeof r=="string")e.style.cssText=r;else{if(typeof n=="string"&&(e.style.cssText=n=""),n)for(t in n)r&&t in r||Ri(e.style,t,"");if(r)for(t in r)n&&r[t]==n[t]||Ri(e.style,t,r[t])}else if(t[0]=="o"&&t[1]=="n")i=t!=(t=t.replace(da,"$1")),a=t.toLowerCase(),t=a in e||t=="onFocusOut"||t=="onFocusIn"?a.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+i]=r,r?n?r.u=n.u:(r.u=lo,e.addEventListener(t,i?ro:to,i)):e.removeEventListener(t,i?ro:to,i);else{if(o=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=r!=null?r:"";break e}catch(s){}typeof r=="function"||(r==null||r===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&r==1?"":r))}}function ji(e){return function(t){if(this.l){var r=this.l[t.type+e];if(t.t==null)t.t=lo++;else if(t.t0?e:Pr(e)?e.map(ya):pt({},e)}function xl(e,t,r,n,o,i,a,s,c){var l,u,p,d,m,h,v,x=r.props,w=t.props,E=t.type;if(E=="svg"?o="http://www.w3.org/2000/svg":E=="math"?o="http://www.w3.org/1998/Math/MathML":o||(o="http://www.w3.org/1999/xhtml"),i!=null){for(l=0;l=r.__.length&&r.__.push({}),r.__[e]}function bn(e){return $r=1,Tl(Ta,e)}function Tl(e,t,r){var n=mo(Hr++,2);if(n.t=e,!n.__c&&(n.__=[r?r(t):Ta(void 0,t),function(s){var c=n.__N?n.__N[0]:n.__[0],l=n.t(c,s);c!==l&&(n.__N=[l,n.__[1]],n.__c.setState({}))}],n.__c=ve,!ve.__f)){var o=function(s,c,l){if(!n.__c.__H)return!0;var u=n.__c.__H.__.filter(function(d){return!!d.__c});if(u.every(function(d){return!d.__N}))return!i||i.call(this,s,c,l);var p=n.__c.props!==s;return u.forEach(function(d){if(d.__N){var m=d.__[0];d.__=d.__N,d.__N=void 0,m!==d.__[0]&&(p=!0)}}),i&&i.call(this,s,c,l)||p};ve.__f=!0;var i=ve.shouldComponentUpdate,a=ve.componentWillUpdate;ve.componentWillUpdate=function(s,c,l){if(this.__e){var u=i;i=void 0,o(s,c,l),i=u}a&&a.call(this,s,c,l)},ve.shouldComponentUpdate=o}return n.__N||n.__}function mt(e,t){var r=mo(Hr++,3);!we.__s&&Ea(r.__H,t)&&(r.__=e,r.u=t,ve.__H.__h.push(r))}function Vt(e){return $r=5,ur(function(){return{current:e}},[])}function ur(e,t){var r=mo(Hr++,7);return Ea(r.__H,t)&&(r.__=e(),r.__H=t,r.__h=e),r.__}function Sl(e,t){return $r=8,ur(function(){return e},t)}function Ol(){for(var e;e=wa.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(mn),e.__H.__h.forEach(oo),e.__H.__h=[]}catch(t){e.__H.__h=[],we.__e(t,e.__v)}}we.__b=function(e){ve=null,Ui&&Ui(e)},we.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),zi&&zi(e,t)},we.__r=function(e){Ni&&Ni(e),Hr=0;var t=(ve=e.__c).__H;t&&(Zn===ve?(t.__h=[],ve.__h=[],t.__.forEach(function(r){r.__N&&(r.__=r.__N),r.u=r.__N=void 0})):(t.__h.forEach(mn),t.__h.forEach(oo),t.__h=[],Hr=0)),Zn=ve},we.diffed=function(e){Di&&Di(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(wa.push(t)!==1&&Fi===we.requestAnimationFrame||((Fi=we.requestAnimationFrame)||Ll)(Ol)),t.__H.__.forEach(function(r){r.u&&(r.__H=r.u),r.u=void 0})),Zn=ve=null},we.__c=function(e,t){t.some(function(r){try{r.__h.forEach(mn),r.__h=r.__h.filter(function(n){return!n.__||oo(n)})}catch(n){t.some(function(o){o.__h&&(o.__h=[])}),t=[],we.__e(n,r.__v)}}),Wi&&Wi(e,t)},we.unmount=function(e){Vi&&Vi(e);var t,r=e.__c;r&&r.__H&&(r.__H.__.forEach(function(n){try{mn(n)}catch(o){t=o}}),r.__H=void 0,t&&we.__e(t,r.__v))};var qi=typeof requestAnimationFrame=="function";function Ll(e){var t,r=function(){clearTimeout(n),qi&&cancelAnimationFrame(t),setTimeout(e)},n=setTimeout(r,35);qi&&(t=requestAnimationFrame(r))}function mn(e){var t=ve,r=e.__c;typeof r=="function"&&(e.__c=void 0,r()),ve=t}function oo(e){var t=ve;e.__c=e.__(),ve=t}function Ea(e,t){return!e||e.length!==t.length||t.some(function(r,n){return r!==e[n]})}function Ta(e,t){return typeof t=="function"?t(e):t}function Ml(e,t){for(var r in t)e[r]=t[r];return e}function Ki(e,t){for(var r in e)if(r!=="__source"&&!(r in t))return!0;for(var n in t)if(n!=="__source"&&e[n]!==t[n])return!0;return!1}function Bi(e,t){this.props=e,this.context=t}(Bi.prototype=new at).isPureReactComponent=!0,Bi.prototype.shouldComponentUpdate=function(e,t){return Ki(this.props,e)||Ki(this.state,t)};var Yi=K.__b;K.__b=function(e){e.type&&e.type.__f&&e.ref&&(e.props.ref=e.ref,e.ref=null),Yi&&Yi(e)};var Yx=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.forward_ref")||3911,kl=K.__e;K.__e=function(e,t,r,n){if(e.then){for(var o,i=t;i=i.__;)if((o=i.__c)&&o.__c)return t.__e==null&&(t.__e=r.__e,t.__k=r.__k),o.__c(e,t)}kl(e,t,r,n)};var Gi=K.unmount;function Sa(e,t,r){return e&&(e.__c&&e.__c.__H&&(e.__c.__H.__.forEach(function(n){typeof n.__c=="function"&&n.__c()}),e.__c.__H=null),(e=Ml({},e)).__c!=null&&(e.__c.__P===r&&(e.__c.__P=t),e.__c.__e=!0,e.__c=null),e.__k=e.__k&&e.__k.map(function(n){return Sa(n,t,r)})),e}function Oa(e,t,r){return e&&r&&(e.__v=null,e.__k=e.__k&&e.__k.map(function(n){return Oa(n,t,r)}),e.__c&&e.__c.__P===t&&(e.__e&&r.appendChild(e.__e),e.__c.__e=!0,e.__c.__P=r)),e}function Qn(){this.__u=0,this.o=null,this.__b=null}function La(e){var t=e.__.__c;return t&&t.__a&&t.__a(e)}function pn(){this.i=null,this.l=null}K.unmount=function(e){var t=e.__c;t&&t.__R&&t.__R(),t&&32&e.__u&&(e.type=null),Gi&&Gi(e)},(Qn.prototype=new at).__c=function(e,t){var r=t.__c,n=this;n.o==null&&(n.o=[]),n.o.push(r);var o=La(n.__v),i=!1,a=function(){i||(i=!0,r.__R=null,o?o(s):s())};r.__R=a;var s=function(){if(!--n.__u){if(n.state.__a){var c=n.state.__a;n.__v.__k[0]=Oa(c,c.__c.__P,c.__c.__O)}var l;for(n.setState({__a:n.__b=null});l=n.o.pop();)l.forceUpdate()}};n.__u++||32&t.__u||n.setState({__a:n.__b=n.__v.__k[0]}),e.then(a,a)},Qn.prototype.componentWillUnmount=function(){this.o=[]},Qn.prototype.render=function(e,t){if(this.__b){if(this.__v.__k){var r=document.createElement("div"),n=this.__v.__k[0].__c;this.__v.__k[0]=Sa(this.__b,r,n.__O=n.__P)}this.__b=null}var o=t.__a&&Wt(ft,null,e.fallback);return o&&(o.__u&=-33),[Wt(ft,null,t.__a?null:e.children),o]};var Ji=function(e,t,r){if(++r[1]===r[0]&&e.l.delete(t),e.props.revealOrder&&(e.props.revealOrder[0]!=="t"||!e.l.size))for(r=e.i;r;){for(;r.length>3;)r.pop()();if(r[1]Object.freeze({get current(){return t.current}}),[])}var Nl=typeof globalThis<"u"&&typeof navigator<"u"&&typeof document<"u";function Dl(e,...t){var r;(r=e==null?void 0:e.addEventListener)==null||r.call(e,...t)}function Wl(e,...t){var r;(r=e==null?void 0:e.removeEventListener)==null||r.call(e,...t)}var Vl=(e,t)=>Object.hasOwn(e,t),zl=()=>!0,ql=()=>!1;function Kl(e=!1){let t=Vt(e),r=Sl(()=>t.current,[]);return mt(()=>(t.current=!0,()=>{t.current=!1}),[]),r}function Bl(e,...t){let r=Kl(),n=ka(t[1]),o=ur(()=>function(...i){r()&&(typeof n.current=="function"?n.current.apply(this,i):typeof n.current.handleEvent=="function"&&n.current.handleEvent.apply(this,i))},[]);mt(()=>{let i=Yl(e)?e.current:e;if(!i)return;let a=t.slice(2);return Dl(i,t[0],o,...a),()=>{Wl(i,t[0],o,...a)}},[e,t[0]])}function Yl(e){return e!==null&&typeof e=="object"&&Vl(e,"current")}var Gl=e=>typeof e=="function"?e:typeof e=="string"?t=>t.key===e:e?zl:ql,Jl=Nl?globalThis:null;function Aa(e,t,r=[],n={}){let{event:o="keydown",target:i=Jl,eventOptions:a}=n,s=ka(t),c=ur(()=>{let l=Gl(e);return function(u){l(u)&&s.current.call(this,u)}},r);Bl(i,o,c,a)}function Ca(e){var t,r,n="";if(typeof e=="string"||typeof e=="number")n+=e;else if(typeof e=="object")if(Array.isArray(e)){var o=e.length;for(t=0;t1)St--;else{for(var e,t=!1;kr!==void 0;){var r=kr;for(kr=void 0,io++;r!==void 0;){var n=r.o;if(r.o=void 0,r.f&=-3,!(8&r.f)&&Pa(r))try{r.c()}catch(o){t||(e=o,t=!0)}r=n}}if(io=0,St--,t)throw e}}function Ql(e){if(St>0)return e();St++;try{return e()}finally{xn()}}var ae=void 0;function Ha(e){var t=ae;ae=void 0;try{return e()}finally{ae=t}}var kr=void 0,St=0,io=0,gn=0;function $a(e){if(ae!==void 0){var t=e.n;if(t===void 0||t.t!==ae)return t={i:0,S:e,p:ae.s,n:void 0,t:ae,e:void 0,x:void 0,r:t},ae.s!==void 0&&(ae.s.n=t),ae.s=t,e.n=t,32&ae.f&&e.S(t),t;if(t.i===-1)return t.i=0,t.n!==void 0&&(t.n.p=t.p,t.p!==void 0&&(t.p.n=t.n),t.p=ae.s,t.n=void 0,ae.s.n=t,ae.s=t),t}}function Ce(e,t){this.v=e,this.i=0,this.n=void 0,this.t=void 0,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}Ce.prototype.brand=Zl;Ce.prototype.h=function(){return!0};Ce.prototype.S=function(e){var t=this,r=this.t;r!==e&&e.e===void 0&&(e.x=r,this.t=e,r!==void 0?r.e=e:Ha(function(){var n;(n=t.W)==null||n.call(t)}))};Ce.prototype.U=function(e){var t=this;if(this.t!==void 0){var r=e.e,n=e.x;r!==void 0&&(r.x=n,e.e=void 0),n!==void 0&&(n.e=r,e.x=void 0),e===this.t&&(this.t=n,n===void 0&&Ha(function(){var o;(o=t.Z)==null||o.call(t)}))}};Ce.prototype.subscribe=function(e){var t=this;return qt(function(){var r=t.value,n=ae;ae=void 0;try{e(r)}finally{ae=n}},{name:"sub"})};Ce.prototype.valueOf=function(){return this.value};Ce.prototype.toString=function(){return this.value+""};Ce.prototype.toJSON=function(){return this.value};Ce.prototype.peek=function(){var e=ae;ae=void 0;try{return this.value}finally{ae=e}};Object.defineProperty(Ce.prototype,"value",{get:function(){var e=$a(this);return e!==void 0&&(e.i=this.i),this.v},set:function(e){if(e!==this.v){if(io>100)throw new Error("Cycle detected");this.v=e,this.i++,gn++,St++;try{for(var t=this.t;t!==void 0;t=t.x)t.t.N()}finally{xn()}}}});function Ot(e,t){return new Ce(e,t)}function Pa(e){for(var t=e.s;t!==void 0;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return!0;return!1}function Ia(e){for(var t=e.s;t!==void 0;t=t.n){var r=t.S.n;if(r!==void 0&&(t.r=r),t.S.n=t,t.i=-1,t.n===void 0){e.s=t;break}}}function Ra(e){for(var t=e.s,r=void 0;t!==void 0;){var n=t.p;t.i===-1?(t.S.U(t),n!==void 0&&(n.n=t.n),t.n!==void 0&&(t.n.p=n)):r=t,t.S.n=t.r,t.r!==void 0&&(t.r=void 0),t=n}e.s=r}function Kt(e,t){Ce.call(this,void 0),this.x=e,this.s=void 0,this.g=gn-1,this.f=4,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}Kt.prototype=new Ce;Kt.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if((36&this.f)==32||(this.f&=-5,this.g===gn))return!0;if(this.g=gn,this.f|=1,this.i>0&&!Pa(this))return this.f&=-2,!0;var e=ae;try{Ia(this),ae=this;var t=this.x();(16&this.f||this.v!==t||this.i===0)&&(this.v=t,this.f&=-17,this.i++)}catch(r){this.v=r,this.f|=16,this.i++}return ae=e,Ra(this),this.f&=-2,!0};Kt.prototype.S=function(e){if(this.t===void 0){this.f|=36;for(var t=this.s;t!==void 0;t=t.n)t.S.S(t)}Ce.prototype.S.call(this,e)};Kt.prototype.U=function(e){if(this.t!==void 0&&(Ce.prototype.U.call(this,e),this.t===void 0)){this.f&=-33;for(var t=this.s;t!==void 0;t=t.n)t.S.U(t)}};Kt.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var e=this.t;e!==void 0;e=e.x)e.t.N()}};Object.defineProperty(Kt.prototype,"value",{get:function(){if(1&this.f)throw new Error("Cycle detected");var e=$a(this);if(this.h(),e!==void 0&&(e.i=this.i),16&this.f)throw this.v;return this.v}});function ta(e,t){return new Kt(e,t)}function ja(e){var t=e.u;if(e.u=void 0,typeof t=="function"){St++;var r=ae;ae=void 0;try{t()}catch(n){throw e.f&=-2,e.f|=8,ho(e),n}finally{ae=r,xn()}}}function ho(e){for(var t=e.s;t!==void 0;t=t.n)t.S.U(t);e.x=void 0,e.s=void 0,ja(e)}function eu(e){if(ae!==this)throw new Error("Out-of-order effect");Ra(this),ae=e,this.f&=-2,8&this.f&&ho(this),xn()}function pr(e,t){this.x=e,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32,this.name=t==null?void 0:t.name}pr.prototype.c=function(){var e=this.S();try{if(8&this.f||this.x===void 0)return;var t=this.x();typeof t=="function"&&(this.u=t)}finally{e()}};pr.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1,this.f&=-9,ja(this),Ia(this),St++;var e=ae;return ae=this,eu.bind(this,e)};pr.prototype.N=function(){2&this.f||(this.f|=2,this.o=kr,kr=this)};pr.prototype.d=function(){this.f|=8,1&this.f||ho(this)};pr.prototype.dispose=function(){this.d()};function qt(e,t){var r=new pr(e,t);try{r.c()}catch(o){throw r.d(),o}var n=r.d.bind(r);return n[Symbol.dispose]=n,n}var Fa,vo,eo,Ua=[];qt(function(){Fa=this.N})();function fr(e,t){K[e]=t.bind(null,K[e]||function(){})}function _n(e){eo&&eo(),eo=e&&e.S()}function Na(e){var t=this,r=e.data,n=ru(r);n.value=r;var o=ur(function(){for(var s=t,c=t.__v;c=c.__;)if(c.__c){c.__c.__$f|=4;break}var l=ta(function(){var m=n.value.value;return m===0?0:m===!0?"":m||""}),u=ta(function(){return!Array.isArray(l.value)&&!pa(l.value)}),p=qt(function(){if(this.N=Da,u.value){var m=l.value;s.__v&&s.__v.__e&&s.__v.__e.nodeType===3&&(s.__v.__e.data=m)}}),d=t.__$u.d;return t.__$u.d=function(){p(),d.call(this)},[u,l]},[]),i=o[0],a=o[1];return i.value?a.peek():a.value}Na.displayName="ReactiveTextNode";Object.defineProperties(Ce.prototype,{constructor:{configurable:!0,value:void 0},type:{configurable:!0,value:Na},props:{configurable:!0,get:function(){return{data:this}}},__b:{configurable:!0,value:1}});fr("__b",function(e,t){if(typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),typeof t.type=="string"){var r,n=t.props;for(var o in n)if(o!=="children"){var i=n[o];i instanceof Ce&&(r||(t.__np=r={}),r[o]=i,n[o]=i.peek())}}e(t)});fr("__r",function(e,t){if(typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.enterComponent(t),t.type!==ft){_n();var r,n=t.__c;n&&(n.__$f&=-2,(r=n.__$u)===void 0&&(n.__$u=r=(function(o){var i;return qt(function(){i=this}),i.c=function(){n.__$f|=1,n.setState({})},i})())),vo=n,_n(r)}e(t)});fr("__e",function(e,t,r,n){typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),_n(),vo=void 0,e(t,r,n)});fr("diffed",function(e,t){typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),_n(),vo=void 0;var r;if(typeof t.type=="string"&&(r=t.__e)){var n=t.__np,o=t.props;if(n){var i=r.U;if(i)for(var a in i){var s=i[a];s!==void 0&&!(a in n)&&(s.d(),i[a]=void 0)}else i={},r.U=i;for(var c in n){var l=i[c],u=n[c];l===void 0?(l=tu(r,c,u,o),i[c]=l):l.o(u,o)}}}e(t)});function tu(e,t,r,n){var o=t in e&&e.ownerSVGElement===void 0,i=Ot(r);return{o:function(a,s){i.value=a,n=s},d:qt(function(){this.N=Da;var a=i.value.value;n[t]!==a&&(n[t]=a,o?e[t]=a:a?e.setAttribute(t,a):e.removeAttribute(t))})}}fr("unmount",function(e,t){if(typeof t.type=="string"){var r=t.__e;if(r){var n=r.U;if(n){r.U=void 0;for(var o in n){var i=n[o];i&&i.d()}}}}else{var a=t.__c;if(a){var s=a.__$u;s&&(a.__$u=void 0,s.d())}}e(t)});fr("__h",function(e,t,r,n){(n<3||n===9)&&(t.__$f|=2),e(t,r,n)});at.prototype.shouldComponentUpdate=function(e,t){var r=this.__$u,n=r&&r.s!==void 0;for(var o in t)return!0;if(this.__f||typeof this.u=="boolean"&&this.u===!0){var i=2&this.__$f;if(!(n||i||4&this.__$f)||1&this.__$f)return!0}else if(!(n||4&this.__$f)||3&this.__$f)return!0;for(var a in e)if(a!=="__source"&&e[a]!==this.props[a])return!0;for(var s in this.props)if(!(s in e))return!0;return!1};function ru(e,t){return bn(function(){return Ot(e,t)})[0]}var nu=function(e){queueMicrotask(function(){queueMicrotask(e)})};function ou(){Ql(function(){for(var e;e=Ua.shift();)Fa.call(e)})}function Da(){Ua.push(this)===1&&(K.requestAnimationFrame||nu)(ou)}var ao=[0];for(let e=0;e<32;e++)ao.push(ao[e]|1<>>5]>>>e&1}set(e){this.data[e>>>5]|=1<<(e&31)}forEach(e){let t=this.size&31;for(let r=0;r{var r;return(r=t.tags)==null?void 0:r.length})&&(matchMedia("(max-width: 768px)").matches||Wa())}function Dt(){Qe.value=He(H({},Qe.value),{hideSearch:!Qe.value.hideSearch})}function Wa(){Qe.value=He(H({},Qe.value),{hideFilters:!Qe.value.hideFilters})}function dn(){return Qe.value.selectedItem}function so(e){Qe.value=He(H({},Qe.value),{selectedItem:e})}function su(){var e,t;return(t=(e=lr.value)==null?void 0:e.items)!=null?t:[]}function wn(){return typeof Se.value.input=="string"?Se.value.input:""}function Va(e){let t=za();e.length&&!t.length?Se.value=He(H({},Se.value),{page:void 0,input:e}):!e.length&&t.length?Se.value=He(H({},Se.value),{page:void 0,input:{type:"operator",data:{operator:"not",operands:[]}}}):Se.value=He(H({},Se.value),{page:void 0,input:e})}function cu(){typeof it.value.pagination.next<"u"&&(Se.value=He(H({},Se.value),{page:it.value.pagination.next}))}function lu(e){let t=Se.value.filter.input;if("type"in t&&t.type==="operator"){for(let r of t.data.operands)if("type"in r&&r.type==="value"&&typeof r.data.value=="string"&&r.data.value===e)return!0}return!1}function za(){let e=Se.value.filter.input,t=[];if("type"in e&&e.type==="operator")for(let r of e.data.operands)"type"in r&&r.type==="value"&&typeof r.data.value=="string"&&t.push(r.data.value);return t}function uu(e){let t=Se.value.filter.input,r=[];if("type"in t&&t.type==="operator")for(let n of t.data.operands)"type"in n&&n.type==="value"&&typeof n.data.value=="string"&&r.push(n.data.value);if(r.includes(e)){let n=r.indexOf(e);n>-1&&r.splice(n,1)}else r.push(e);Se.value=He(H({},Se.value),{page:void 0,filter:He(H({},Se.value.filter),{input:{type:"operator",data:{operator:"and",operands:r.map(n=>({type:"value",data:{field:"tags",value:n}}))}}})}),Va(wn())}function pu(){return it.value.items}function fu(){return it.value.total}function mu(){var e;for(let t of(e=it.value.aggregations)!=null?e:[])if(t.type==="term")return t.data.value;return[]}function sr(){return Qe.value.hideSearch}function du(){return Qe.value.hideFilters}function qa(){var e;return(e=Ka.value.highlight)!=null?e:!1}var Qe=Ot({hideSearch:!0,hideFilters:!0,selectedItem:0}),Ka=Ot({}),lr=Ot(),na=Ot(),Se=Ot({input:"",filter:{input:{type:"operator",data:{operator:"and",operands:[]}},aggregation:{input:[{type:"term",data:{field:"tags"}}]}}}),it=Ot({items:[],query:{select:{documents:new ra(0),terms:new ra(0)},values:[]},pagination:{total:0}});function hu(e,t,r){for(let n=0;tr&&t(0,o,r,r=i);continue;case 62:e.charCodeAt(r+1)===47?t(2,--o,r,r=i+1):hu(e,r,n)?t(3,o,r,r=i+1):t(1,o++,r,r=i+1)}i>r&&t(0,o,r,i)}function bu(e,t=0,r=e.length){let n=++t;e:for(let l=0;n{let i=[],a=[],{onElement:s,onText:c=gu}=typeof r=="function"?{onElement:r}:r,l=0,u=0;return e(t,(p,d,m,h)=>{if(p===0)i[l++]=c(t,m,h),a[u++]={value:null,depth:d};else if(p&1&&(a[u++]={value:bu(t,m,h),depth:d}),p&2)for(let v=0;u>=0;v++){let{value:x,depth:w}=a[--u];if(w>d)continue;let E=i.slice(l-=v,l+v);i[l++]=s(x,E),u++;break}},n,o),i.slice(0,l)}}function yu(e){return e.replace(/[&<>]/g,t=>{switch(t.charCodeAt(0)){case 38:return"&";case 60:return"<";case 62:return">"}})}function hn(e){return e.replace(/&(amp|[lg]t);/g,t=>{switch(t.charCodeAt(1)){case 97:return"&";case 108:return"<";case 103:return">"}})}function xu(e,t){return{start:e.start+t,end:e.end+t,value:e.value}}function wu(e,t,r){return e.slice(t,r)}function Eu(e){let{onHighlight:t,onText:r=wu}=typeof e=="function"?{onHighlight:e}:e;return(n,o,i=0,a=n.length)=>{var l;let s=[],c=(l=o==null?void 0:o.ranges)!=null?l:[];for(let u=0,p=i;ua)break;let m=c[u].end;if(mi&&s.push(r(n,i,d));let{value:h}=c[u];s.push(t(n,{start:d,end:i=m,value:h}))}return i{let o=n.data;switch(o.type){case 1:na.value=!0;break;case 3:typeof o.data.pagination.prev<"u"?it.value=He(H({},it.value),{pagination:o.data.pagination,items:[...it.value.items,...o.data.items]}):(it.value=o.data,so(0));break}},qt(()=>{lr.value&&r.postMessage({type:0,data:lr.value})}),qt(()=>{na.value&&r.postMessage({type:2,data:Se.value})})}var oa={container:"p",hidden:"m"};function ku(e){return z("div",{class:zt(oa.container,{[oa.hidden]:e.hidden}),onClick:()=>Dt()})}var ia={container:"r",disabled:"c"};function co(e){return z("button",{class:zt(ia.container,{[ia.disabled]:!e.onClick}),onClick:e.onClick,children:e.children})}var aa=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),Au=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,r,n)=>n?n.toUpperCase():r.toLowerCase()),sa=e=>{let t=Au(e);return t.charAt(0).toUpperCase()+t.slice(1)},Cu=(...e)=>e.filter((t,r,n)=>!!t&&t.trim()!==""&&n.indexOf(t)===r).join(" ").trim(),Hu={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},$u=c=>{var l=c,{color:e="currentColor",size:t=24,strokeWidth:r=2,absoluteStrokeWidth:n,children:o,iconNode:i,class:a=""}=l,s=gr(l,["color","size","strokeWidth","absoluteStrokeWidth","children","iconNode","class"]);return Wt("svg",H(He(H({},Hu),{width:String(t),height:t,stroke:e,"stroke-width":n?Number(r)*24/Number(t):r,class:["lucide",a].join(" ")}),s),[...i.map(([u,p])=>Wt(u,p)),...Cr(o)])},bo=(e,t)=>{let r=a=>{var s=a,{class:n="",children:o}=s,i=gr(s,["class","children"]);return Wt($u,He(H({},i),{iconNode:t,class:Cu(`lucide-${aa(sa(e))}`,`lucide-${aa(e)}`,n)}),o)};return r.displayName=sa(e),r},Pu=bo("corner-down-left",[["path",{d:"M20 4v7a4 4 0 0 1-4 4H4",key:"6o5b7l"}],["path",{d:"m9 10-5 5 5 5",key:"1kshq7"}]]),Iu=bo("list-filter",[["path",{d:"M2 5h20",key:"1fs1ex"}],["path",{d:"M6 12h12",key:"8npq4p"}],["path",{d:"M9 19h6",key:"456am0"}]]),Ru=bo("search",[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]]),Gx=hl(vl(),1);function ju({threshold:e=0,root:t=null,rootMargin:r="0%",freezeOnceVisible:n=!1,initialIsIntersecting:o=!1,onChange:i}={}){var a;let[s,c]=bn(null),[l,u]=bn(()=>({isIntersecting:o,entry:void 0})),p=Vt();p.current=i;let d=((a=l.entry)==null?void 0:a.isIntersecting)&&n;mt(()=>{if(!s||!("IntersectionObserver"in window)||d)return;let v,x=new IntersectionObserver(w=>{let E=Array.isArray(x.thresholds)?x.thresholds:[x.thresholds];w.forEach(_=>{let de=_.isIntersecting&&E.some(be=>_.intersectionRatio>=be);u({isIntersecting:de,entry:_}),p.current&&p.current(de,_),de&&n&&v&&(v(),v=void 0)})},{threshold:e,root:t,rootMargin:r});return x.observe(s),()=>{x.disconnect()}},[s,JSON.stringify(e),t,r,d,n]);let m=Vt(null);mt(()=>{var v;!s&&(v=l.entry)!=null&&v.target&&!n&&!d&&m.current!==l.entry.target&&(m.current=l.entry.target,u({isIntersecting:o,entry:void 0}))},[s,l.entry,n,d,o]);let h=[c,!!l.isIntersecting,l.entry];return h.ref=h[0],h.isIntersecting=h[1],h.entry=h[2],h}var lt={container:"n",hidden:"l",content:"u",pop:"d",badge:"y",sidebar:"i",controls:"w",results:"k",loadmore:"z"};function Fu(e){let{isIntersecting:t,ref:r}=ju({threshold:0});mt(()=>{t&&cu()},[t]);let n=Vt(null);mt(()=>{n.current&&typeof Se.value.page>"u"&&n.current.scrollTo({top:0,behavior:"smooth"})},[Se.value]);let o=za();return z("div",{class:zt(lt.container,{[lt.hidden]:e.hidden}),children:[z("div",{class:lt.content,children:[z("div",{class:lt.controls,children:[z(co,{onClick:Dt,children:z(Ru,{})}),z(Nu,{focus:!e.hidden}),z(co,{onClick:Wa,children:[z(Iu,{}),o.length>0&&z("span",{class:lt.badge,children:o.length})]})]}),z("div",{class:lt.results,ref:n,children:[z(Du,{keyboard:!e.hidden}),z("div",{class:lt.loadmore,ref:r})]})]}),z("div",{class:zt(lt.sidebar,{[lt.hidden]:du()}),children:z(Uu,{})})]})}var Tt={container:"X",list:"j",heading:"F",title:"I",item:"o",active:"g",value:"R",count:"q"};function Uu(e){let t=mu();return t.sort((r,n)=>n.node.count-r.node.count),z("div",{class:Tt.container,children:[z("h3",{class:Tt.heading,children:"Filters"}),z("h4",{class:Tt.title,children:"Tags"}),z("ol",{class:Tt.list,children:t.map(r=>z("li",{class:zt(Tt.item,{[Tt.active]:lu(r.node.value)}),onClick:()=>uu(r.node.value),children:[z("span",{class:Tt.value,children:r.node.value}),z("span",{class:Tt.count,children:r.node.count})]}))})]})}var ca={container:"f"};function Nu(e){let t=Vt(null);return mt(()=>{var r,n;e.focus?(r=t.current)==null||r.focus():(n=t.current)==null||n.blur()},[e.focus]),z("div",{class:ca.container,children:z("input",{ref:t,type:"text",class:ca.content,value:hn(wn()),onInput:r=>Va(yu(r.currentTarget.value)),autocapitalize:"off",autocomplete:"off",autocorrect:"off",placeholder:"Search",spellcheck:!1,role:"combobox"})})}var ut={container:"b",heading:"A",item:"a",active:"h",wrapper:"B",actions:"s",title:"x",path:"t"};function Ga(){let[e,t]=bn(!1);return mt(()=>{let r=()=>t(!0),n=()=>t(!1);return document.addEventListener("compositionstart",r),document.addEventListener("compositionend",n),()=>{document.removeEventListener("compositionstart",r),document.removeEventListener("compositionend",n)}},[]),e}function Du(e){var s;let t=su(),r=pu(),n=dn(),o=Vt([]),i=Ga();mt(()=>{let c=o.current[n];c&&c.scrollIntoView({block:"center",behavior:"smooth"})},[n]),Aa(e.keyboard,c=>{if(i)return;let l=dn();c.key==="ArrowDown"?(c.preventDefault(),so(Math.min(l+1,r.length-1))):c.key==="ArrowUp"&&(c.preventDefault(),so(Math.max(l-1,0)))},[e.keyboard,i]);let a=(s=fu())!=null?s:0;return z(ft,{children:[r.length>0&&z("h3",{class:ut.heading,children:[z("span",{class:ut.bubble,children:new Intl.NumberFormat("en-US").format(a)})," ","results"]}),z("ol",{class:ut.container,children:r.map((c,l)=>{var m;let u=Ba(t[c.id].title,c.matches.find(({field:h})=>h==="title")),p=Mu((m=t[c.id].path)!=null?m:[],c.matches.find(({field:h})=>h==="path")),d=t[c.id].location;if(qa()){let h=encodeURIComponent(wn()),[v,x]=d.split("#",2);d=`${v}?h=${h.replace(/%20/g,"+")}`,typeof x<"u"&&(d+=`#${x}`)}return z("li",{children:z("a",{ref:h=>{o.current[l]=h},href:d,onClick:()=>Dt(),class:zt(ut.item,{[ut.active]:l===dn()}),children:[z("div",{class:ut.wrapper,children:[z("h2",{class:ut.title,children:u}),z("menu",{class:ut.path,children:p.map(h=>z("li",{children:h}))})]}),z("nav",{class:ut.actions,children:z(co,{children:z(Pu,{})})})]})})})})]})}var Wu={container:"e"};function Vu(e){let t=Ga();return Aa(!0,r=>{var n,o,i,a,s;if(!t)if((r.metaKey||r.ctrlKey)&&r.key==="k")r.preventDefault(),Dt();else if((r.metaKey||r.ctrlKey)&&r.key==="j")document.body.classList.toggle("dark");else if(r.key==="Enter"&&!sr()){r.preventDefault();let c=dn(),l=(o=(n=it.value)==null?void 0:n.items[c])==null?void 0:o.id;if((a=(i=lr.value)==null?void 0:i.items[l])!=null&&a.location){Dt();let u=(s=lr.value)==null?void 0:s.items[l].location;if(qa()){let p=encodeURIComponent(wn()),[d,m]=u.split("#",2);u=`${d}?h=${p.replace(/%20/g,"+")}`,typeof m<"u"&&(u+=`#${m}`)}window.location.href=u}}else r.key==="Escape"&&!sr()&&(r.preventDefault(),Dt())},[t]),z("div",{class:Wu.container,children:[z(ku,{hidden:sr()}),z(Fu,{hidden:sr()})]})}function Ja(e,t){au(e),El(z(Vu,{}),t)}function go(){Dt()}function zu(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function qu(){return R(b(window,"compositionstart").pipe(f(()=>!0)),b(window,"compositionend").pipe(f(()=>!1))).pipe(J(!1))}function Xa(){let e=b(window,"keydown").pipe(f(t=>({mode:sr()?"global":"search",type:t.key,meta:t.ctrlKey||t.metaKey,claim(){t.preventDefault(),t.stopPropagation()}})),L(({mode:t,type:r})=>{if(t==="global"){let n=xt();if(typeof n!="undefined")return!zu(n,r)}return!0}),xe());return qu().pipe(g(t=>t?y:e))}function Ye(){return new URL(location.href)}function dt(e,t=!1){if(X("navigation.instant")&&!t){let r=A("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function Za(){return new I}function Qa(){return location.hash.slice(1)}function es(e){let t=A("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function _o(e){return R(b(window,"hashchange"),e).pipe(f(Qa),J(Qa()),L(t=>t.length>0),se(1))}function ts(e){return _o(e).pipe(f(t=>Le(`[id="${t}"]`)),L(t=>typeof t!="undefined"))}function Ir(e){let t=matchMedia(e);return an(r=>t.addListener(()=>r(t.matches))).pipe(J(t.matches))}function rs(){let e=matchMedia("print");return R(b(window,"beforeprint").pipe(f(()=>!0)),b(window,"afterprint").pipe(f(()=>!1))).pipe(J(e.matches))}function yo(e,t){return e.pipe(g(r=>r?t():y))}function xo(e,t){return new U(r=>{let n=new XMLHttpRequest;return n.open("GET",`${e}`),n.responseType="blob",n.addEventListener("load",()=>{n.status>=200&&n.status<300?(r.next(n.response),r.complete()):r.error(new Error(n.statusText))}),n.addEventListener("error",()=>{r.error(new Error("Network error"))}),n.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(n.addEventListener("progress",o=>{var i;if(o.lengthComputable)t.progress$.next(o.loaded/o.total*100);else{let a=(i=n.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(o.loaded/+a*100)}}),t.progress$.next(5)),n.send(),()=>n.abort()})}function et(e,t){return xo(e,t).pipe(g(r=>r.text()),f(r=>JSON.parse(r)),se(1))}function En(e,t){let r=new DOMParser;return xo(e,t).pipe(g(n=>n.text()),f(n=>r.parseFromString(n,"text/html")),se(1))}function ns(e,t){let r=new DOMParser;return xo(e,t).pipe(g(n=>n.text()),f(n=>r.parseFromString(n,"text/xml")),se(1))}var wo={drawer:G("[data-md-toggle=drawer]"),search:G("[data-md-toggle=search]")};function Eo(e,t){wo[e].checked!==t&&wo[e].click()}function Tn(e){let t=wo[e];return b(t,"change").pipe(f(()=>t.checked),J(t.checked))}function os(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function is(){return R(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(f(os),J(os()))}function as(){return{width:innerWidth,height:innerHeight}}function ss(){return b(window,"resize",{passive:!0}).pipe(f(as),J(as()))}function cs(){return re([is(),ss()]).pipe(f(([e,t])=>({offset:e,size:t})),se(1))}function Sn(e,{viewport$:t,header$:r}){let n=t.pipe(fe("size")),o=re([n,r]).pipe(f(()=>wt(e)));return re([r,t,o]).pipe(f(([{height:i},{offset:a,size:s},{x:c,y:l}])=>({offset:{x:a.x-c,y:a.y-l+i},size:s})))}var Ku=G("#__config"),mr=JSON.parse(Ku.textContent);mr.base=`${new URL(mr.base,Ye())}`;function Ue(){return mr}function X(e){return mr.features.includes(e)}function Bt(e,t){return typeof t!="undefined"?mr.translations[e].replace("#",t.toString()):mr.translations[e]}function ht(e,t=document){return G(`[data-md-component=${e}]`,t)}function Ee(e,t=document){return P(`[data-md-component=${e}]`,t)}function Bu(e){let t=G(".md-typeset > :first-child",e);return b(t,"click",{once:!0}).pipe(f(()=>G(".md-typeset",e)),f(r=>({hash:__md_hash(r.innerHTML)})))}function ls(e){if(!X("announce.dismiss")||!e.childElementCount)return y;if(!e.hidden){let t=G(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return j(()=>{let t=new I;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),Bu(e).pipe($(r=>t.next(r)),V(()=>t.complete()),f(r=>H({ref:e},r)))})}function Yu(e,{target$:t}){return t.pipe(f(r=>({hidden:r!==e})))}function us(e,t){let r=new I;return r.subscribe(({hidden:n})=>{e.hidden=n}),Yu(e,t).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))}function To(e,t){return t==="inline"?A("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},A("div",{class:"md-tooltip__inner md-typeset"})):A("div",{class:"md-tooltip",id:e,role:"tooltip"},A("div",{class:"md-tooltip__inner md-typeset"}))}function On(...e){return A("div",{class:"md-tooltip2",role:"dialog"},A("div",{class:"md-tooltip2__inner md-typeset"},e))}function ps(...e){return A("div",{class:"md-tooltip2",role:"tooltip"},A("div",{class:"md-tooltip2__inner md-typeset"},e))}function fs(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return A("aside",{class:"md-annotation",tabIndex:0},To(t),A("a",{href:r,class:"md-annotation__index",tabIndex:-1},A("span",{"data-md-annotation-id":e})))}else return A("aside",{class:"md-annotation",tabIndex:0},To(t),A("span",{class:"md-annotation__index",tabIndex:-1},A("span",{"data-md-annotation-id":e})))}function ms(e){return A("button",{class:"md-code__button",title:Bt("clipboard.copy"),"data-clipboard-target":`#${e} > code`,"data-md-type":"copy"})}function ds(){return A("button",{class:"md-code__button",title:"Toggle line selection","data-md-type":"select"})}function hs(){return A("nav",{class:"md-code__nav"})}var Xu=_r(So());function bs(e){return A("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>A("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?Li(r):r)))}function Oo(e){let t=`tabbed-control tabbed-control--${e}`;return A("div",{class:t,hidden:!0},A("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function gs(e){return A("div",{class:"md-typeset__scrollwrap"},A("div",{class:"md-typeset__table"},e))}function Zu(e){var n;let t=Ue(),r=new URL(`../${e.version}/`,t.base);return A("li",{class:"md-version__item"},A("a",{href:`${r}`,class:"md-version__link"},e.title,((n=t.version)==null?void 0:n.alias)&&e.aliases.length>0&&A("span",{class:"md-version__alias"},e.aliases[0])))}function _s(e,t){var n;let r=Ue();return e=e.filter(o=>{var i;return!((i=o.properties)!=null&&i.hidden)}),A("div",{class:"md-version"},A("button",{class:"md-version__current","aria-label":Bt("select.version")},t.title,((n=r.version)==null?void 0:n.alias)&&t.aliases.length>0&&A("span",{class:"md-version__alias"},t.aliases[0])),A("ul",{class:"md-version__list"},e.map(Zu)))}var Qu=0;function ep(e,t=250){let r=re([ir(e),Ft(e,t)]).pipe(f(([o,i])=>o||i),ie()),n=j(()=>Ai(e)).pipe(oe(Ut),Lr(1),Ze(r),f(()=>Ci(e)));return r.pipe(Sr(o=>o),g(()=>re([r,n])),f(([o,i])=>({active:o,offset:i})),xe())}function Rr(e,t,r=250){let{content$:n,viewport$:o}=t,i=`__tooltip2_${Qu++}`;return j(()=>{let a=new I,s=new Un(!1);a.pipe(he(),ye(!1)).subscribe(s);let c=s.pipe(Tr(u=>Ve(+!u*250,Wn)),ie(),g(u=>u?n:y),$(u=>u.id=i),xe());re([a.pipe(f(({active:u})=>u)),c.pipe(g(u=>Ft(u,250)),J(!1))]).pipe(f(u=>u.some(p=>p))).subscribe(s);let l=s.pipe(L(u=>u),pe(c,o),f(([u,p,{size:d}])=>{let m=e.getBoundingClientRect(),h=m.width/2;if(p.role==="tooltip")return{x:h,y:8+m.height};if(m.y>=d.height/2){let{height:v}=Ae(p);return{x:h,y:-16-v}}else return{x:h,y:16+m.height}}));return re([c,a,l]).subscribe(([u,{offset:p},d])=>{u.style.setProperty("--md-tooltip-host-x",`${p.x}px`),u.style.setProperty("--md-tooltip-host-y",`${p.y}px`),u.style.setProperty("--md-tooltip-x",`${d.x}px`),u.style.setProperty("--md-tooltip-y",`${d.y}px`),u.classList.toggle("md-tooltip2--top",d.y<0),u.classList.toggle("md-tooltip2--bottom",d.y>=0)}),s.pipe(L(u=>u),pe(c,(u,p)=>p),L(u=>u.role==="tooltip")).subscribe(u=>{let p=Ae(G(":scope > *",u));u.style.setProperty("--md-tooltip-width",`${p.width}px`),u.style.setProperty("--md-tooltip-tail","0px")}),s.pipe(ie(),Ie(je),pe(c)).subscribe(([u,p])=>{p.classList.toggle("md-tooltip2--active",u)}),re([s.pipe(L(u=>u)),c]).subscribe(([u,p])=>{p.role==="dialog"?(e.setAttribute("aria-controls",i),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",i)}),s.pipe(L(u=>!u)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ep(e,r).pipe($(u=>a.next(u)),V(()=>a.complete()),f(u=>H({ref:e},u)))})}function Ge(e,{viewport$:t},r=document.body){return Rr(e,{content$:new U(n=>{let o=e.title,i=ps(o);return n.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",o)}}),viewport$:t},0)}function tp(e,t){let r=j(()=>re([Hi(e),Ut(t)])).pipe(f(([{x:n,y:o},i])=>{let{width:a,height:s}=Ae(e);return{x:n-i.x+a/2,y:o-i.y+s/2}}));return ir(e).pipe(g(n=>r.pipe(f(o=>({active:n,offset:o})),Me(+!n||1/0))))}function ys(e,t,{target$:r}){let[n,o]=Array.from(e.children);return j(()=>{let i=new I,a=i.pipe(he(),ye(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),Et(e).pipe(Q(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),R(i.pipe(L(({active:s})=>s)),i.pipe(Be(250),L(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(n):n.remove()},complete(){e.prepend(n)}}),i.pipe(Xe(16,je)).subscribe(({active:s})=>{n.classList.toggle("md-tooltip--active",s)}),i.pipe(Lr(125,je),L(()=>!!e.offsetParent),f(()=>e.offsetParent.getBoundingClientRect()),f(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),b(o,"click").pipe(Q(a),L(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),b(o,"mousedown").pipe(Q(a),pe(i)).subscribe(([s,{active:c}])=>{var l;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(c){s.preventDefault();let u=e.parentElement.closest(".md-annotation");u instanceof HTMLElement?u.focus():(l=xt())==null||l.blur()}}),r.pipe(Q(a),L(s=>s===n),It(125)).subscribe(()=>e.focus()),tp(e,t).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))})}function rp(e){let t=Ue();if(e.tagName!=="CODE")return[e];let r=[".c",".c1",".cm"];if(t.annotate){let n=e.closest("[class|=language]");if(n)for(let o of Array.from(n.classList)){if(!o.startsWith("language-"))continue;let[,i]=o.split("-");i in t.annotate&&r.push(...t.annotate[i])}}return P(r.join(", "),e)}function np(e){let t=[];for(let r of rp(e)){let n=[],o=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=o.nextNode();i;i=o.nextNode())n.push(i);for(let i of n){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,c]=a;if(typeof c=="undefined"){let l=i.splitText(a.index);i=l.splitText(s.length),t.push(l)}else{i.textContent=s,t.push(i);break}}}}return t}function xs(e,t){t.append(...Array.from(e.childNodes))}function Ln(e,t,{target$:r,print$:n}){let o=t.closest("[id]"),i=o==null?void 0:o.id,a=new Map;for(let s of np(t)){let[,c]=s.textContent.match(/\((\d+)\)/);Le(`:scope > li:nth-child(${c})`,e)&&(a.set(c,fs(c,i)),s.replaceWith(a.get(c)))}return a.size===0?y:j(()=>{let s=new I,c=s.pipe(he(),ye(!0)),l=[];for(let[u,p]of a)l.push([G(".md-typeset",p),G(`:scope > li:nth-child(${u})`,e)]);return n.pipe(Q(c)).subscribe(u=>{e.hidden=!u,e.classList.toggle("md-annotation-list",u);for(let[p,d]of l)u?xs(p,d):xs(d,p)}),R(...[...a].map(([,u])=>ys(u,t,{target$:r}))).pipe(V(()=>s.complete()),xe())})}function ws(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return ws(t)}}function Es(e,t){return j(()=>{let r=ws(e);return typeof r!="undefined"?Ln(r,e,t):y})}var Ss=_r(Mo());var op=0,Ts=R(b(window,"keydown").pipe(f(()=>!0)),R(b(window,"keyup"),b(window,"contextmenu")).pipe(f(()=>!1))).pipe(J(!1),se(1));function Os(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Os(t)}}function ip(e){return Re(e).pipe(f(({width:t})=>({scrollable:Mr(e).width>t})),fe("scrollable"))}function Ls(e,t){let{matches:r}=matchMedia("(hover)"),n=j(()=>{let o=new I,i=o.pipe(Gn(1));o.subscribe(({scrollable:m})=>{m&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[],s=e.closest("pre"),c=s.closest("[id]"),l=c?c.id:op++;s.id=`__code_${l}`;let u=[],p=e.closest(".highlight");if(p instanceof HTMLElement){let m=Os(p);if(typeof m!="undefined"&&(p.classList.contains("annotate")||X("content.code.annotate"))){let h=Ln(m,e,t);u.push(Re(p).pipe(Q(i),f(({width:v,height:x})=>v&&x),ie(),g(v=>v?h:y)))}}let d=P(":scope > span[id]",e);if(d.length&&(e.classList.add("md-code__content"),e.closest(".select")||X("content.code.select")&&!e.closest(".no-select"))){let m=+d[0].id.split("-").pop(),h=ds();a.push(h),X("content.tooltips")&&u.push(Ge(h,{viewport$}));let v=b(h,"click").pipe(Or(M=>!M,!1),$(()=>h.blur()),xe());v.subscribe(M=>{h.classList.toggle("md-code__button--active",M)});let x=me(d).pipe(oe(M=>Ft(M).pipe(f(O=>[M,O]))));v.pipe(g(M=>M?x:y)).subscribe(([M,O])=>{let N=Le(".hll.select",M);if(N&&!O)N.replaceWith(...Array.from(N.childNodes));else if(!N&&O){let ee=document.createElement("span");ee.className="hll select",ee.append(...Array.from(M.childNodes).slice(1)),M.append(ee)}});let w=me(d).pipe(oe(M=>b(M,"mousedown").pipe($(O=>O.preventDefault()),f(()=>M)))),E=v.pipe(g(M=>M?w:y),pe(Ts),f(([M,O])=>{var ee;let N=d.indexOf(M)+m;if(O===!1)return[N,N];{let le=P(".hll",e).map(ce=>d.indexOf(ce.parentElement)+m);return(ee=window.getSelection())==null||ee.removeAllRanges(),[Math.min(N,...le),Math.max(N,...le)]}})),_=_o(y).pipe(L(M=>M.startsWith(`__codelineno-${l}-`)));_.subscribe(M=>{let[,,O]=M.split("-"),N=O.split(":").map(le=>+le-m+1);N.length===1&&N.push(N[0]);for(let le of P(".hll:not(.select)",e))le.replaceWith(...Array.from(le.childNodes));let ee=d.slice(N[0]-1,N[1]);for(let le of ee){let ce=document.createElement("span");ce.className="hll",ce.append(...Array.from(le.childNodes).slice(1)),le.append(ce)}}),_.pipe(Me(1),Ie(ge)).subscribe(M=>{if(M.includes(":")){let O=document.getElementById(M.split(":")[0]);O&&setTimeout(()=>{let N=O,ee=-64;for(;N!==document.body;)ee+=N.offsetTop,N=N.offsetParent;window.scrollTo({top:ee})},1)}});let be=me(P('a[href^="#__codelineno"]',p)).pipe(oe(M=>b(M,"click").pipe($(O=>O.preventDefault()),f(()=>M)))).pipe(Q(i),pe(Ts),f(([M,O])=>{let ee=+G(`[id="${M.hash.slice(1)}"]`).parentElement.id.split("-").pop();if(O===!1)return[ee,ee];{let le=P(".hll",e).map(ce=>+ce.parentElement.id.split("-").pop());return[Math.min(ee,...le),Math.max(ee,...le)]}}));R(E,be).subscribe(M=>{let O=`#__codelineno-${l}-`;M[0]===M[1]?O+=M[0]:O+=`${M[0]}:${M[1]}`,history.replaceState({},"",O),window.dispatchEvent(new HashChangeEvent("hashchange",{newURL:window.location.origin+window.location.pathname+O,oldURL:window.location.href}))})}if(Ss.default.isSupported()&&(e.closest(".copy")||X("content.code.copy")&&!e.closest(".no-copy"))){let m=ms(s.id);a.push(m),X("content.tooltips")&&u.push(Ge(m,{viewport$}))}if(a.length){let m=hs();m.append(...a),s.insertBefore(m,e)}return ip(e).pipe($(m=>o.next(m)),V(()=>o.complete()),f(m=>H({ref:e},m)),Rt(R(...u).pipe(Q(i))))});return X("content.lazy")?Et(e).pipe(L(o=>o),Me(1),g(()=>n)):n}function ap(e,{target$:t,print$:r}){let n=!0;return R(t.pipe(f(o=>o.closest("details:not([open])")),L(o=>e===o),f(()=>({action:"open",reveal:!0}))),r.pipe(L(o=>o||!n),$(()=>n=e.open),f(o=>({action:o?"open":"close"}))))}function Ms(e,t){return j(()=>{let r=new I;return r.subscribe(({action:n,reveal:o})=>{e.toggleAttribute("open",n==="open"),o&&e.scrollIntoView()}),ap(e,t).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}var ks=0,As=new Map;function sp(e){let t=document.createElement("h3");t.innerHTML=e.innerHTML;let r=[t],n=e.nextElementSibling;for(;n&&!(n instanceof HTMLHeadingElement);)r.push(n.cloneNode(!0)),n=n.nextElementSibling;return r}function cp(e,t){for(let r of P("[href], [src]",e))for(let n of["href","src"]){let o=r.getAttribute(n);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){r[n]=new URL(r.getAttribute(n),t).toString();break}}for(let r of P("[name^=__], [for]",e))for(let n of["id","for","name"]){let o=r.getAttribute(n);o&&r.setAttribute(n,`${o}$preview_${ks}`)}return ks++,Y(e)}function lp(e){let t=As.get(e.toString());return t?Y(t):En(e).pipe(g(r=>cp(r,e)),f(r=>(As.set(e.toString(),r),r)))}function Cs(e,t){let{sitemap$:r}=t;if(!(e instanceof HTMLAnchorElement))return y;if(!(X("navigation.instant.preview")||e.hasAttribute("data-preview")))return y;e.removeAttribute("title");let n=re([ir(e),Ft(e).pipe(ke(1))]).pipe(f(([i,a])=>i||a),ie(),L(i=>i));return $t([r,n]).pipe(g(([i])=>{let a=new URL(e.href);return a.search=a.hash="",i.has(`${a}`)?Y(a):y}),g(i=>lp(i)),g(i=>{let a=e.hash?`article [id="${decodeURIComponent(e.hash.slice(1))}"]`:"article h1",s=Le(a,i);return typeof s=="undefined"?y:Y(sp(s))})).pipe(g(i=>{let a=new U(s=>{let c=On(...i);return s.next(c),document.body.append(c),()=>c.remove()});return Rr(e,H({content$:a},t))}))}var Hs=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs marker.marker.composition.class path,defs marker.marker.dependency.class path,defs marker.marker.extension.class path{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs marker.marker.aggregation.class path{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}[id^=entity] path,[id^=entity] rect{fill:var(--md-default-bg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs .marker.oneOrMore.er *,defs .marker.onlyOne.er *,defs .marker.zeroOrMore.er *,defs .marker.zeroOrOne.er *{stroke:var(--md-mermaid-edge-color)!important}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var ko,pp=0;function fp(){return typeof mermaid=="undefined"||mermaid instanceof Element?ar("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):Y(void 0)}function $s(e){return e.classList.remove("mermaid"),ko||(ko=fp().pipe($(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Hs,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),f(()=>{}),se(1))),ko.subscribe(()=>Uo(null,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${pp++}`,r=A("div",{class:"mermaid"}),n=e.textContent,{svg:o,fn:i}=yield mermaid.render(t,n),a=r.attachShadow({mode:"closed"});a.innerHTML=o,e.replaceWith(r),i==null||i(a)})),ko.pipe(f(()=>({ref:e})))}var Ps=A("table");function Is(e){return e.replaceWith(Ps),Ps.replaceWith(gs(e)),Y({ref:e})}function mp(e){let t=e.find(r=>r.checked)||e[0];return R(...e.map(r=>b(r,"change").pipe(f(()=>G(`label[for="${r.id}"]`))))).pipe(J(G(`label[for="${t.id}"]`)),f(r=>({active:r})))}function Rs(e,{viewport$:t,target$:r}){let n=G(".tabbed-labels",e),o=P(":scope > input",e),i=Oo("prev");e.append(i);let a=Oo("next");return e.append(a),j(()=>{let s=new I,c=s.pipe(he(),ye(!0));re([s,Re(e),Et(e)]).pipe(Q(c),Xe(1,je)).subscribe({next([{active:l},u]){let p=wt(l),{width:d}=Ae(l);e.style.setProperty("--md-indicator-x",`${p.x}px`),e.style.setProperty("--md-indicator-width",`${d}px`);let m=ln(n);(p.xm.x+u.width)&&n.scrollTo({left:Math.max(0,p.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),re([Ut(n),Re(n)]).pipe(Q(c)).subscribe(([l,u])=>{let p=Mr(n);i.hidden=l.x<16,a.hidden=l.x>p.width-u.width-16}),R(b(i,"click").pipe(f(()=>-1)),b(a,"click").pipe(f(()=>1))).pipe(Q(c)).subscribe(l=>{let{width:u}=Ae(n);n.scrollBy({left:u*l,behavior:"smooth"})}),r.pipe(Q(c),L(l=>o.includes(l))).subscribe(l=>l.click()),n.classList.add("tabbed-labels--linked");for(let l of o){let u=G(`label[for="${l.id}"]`);u.replaceChildren(A("a",{href:`#${u.htmlFor}`,tabIndex:-1},...Array.from(u.childNodes))),b(u.firstElementChild,"click").pipe(Q(c),L(p=>!(p.metaKey||p.ctrlKey)),$(p=>{p.preventDefault(),p.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${u.htmlFor}`),u.click()})}return X("content.tabs.link")&&s.pipe(ke(1),pe(t)).subscribe(([{active:l},{offset:u}])=>{let p=l.innerText.trim();if(l.hasAttribute("data-md-switching"))l.removeAttribute("data-md-switching");else{let d=e.offsetTop-u.y;for(let h of P("[data-tabs]"))for(let v of P(":scope > input",h)){let x=G(`label[for="${v.id}"]`);if(x!==l&&x.innerText.trim()===p){x.setAttribute("data-md-switching",""),v.click();break}}window.scrollTo({top:e.offsetTop-d});let m=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([p,...m])])}}),s.pipe(Q(c)).subscribe(()=>{for(let l of P("audio, video",e))l.offsetWidth&&l.autoplay?l.play().catch(()=>{}):l.pause()}),mp(o).pipe($(l=>s.next(l)),V(()=>s.complete()),f(l=>H({ref:e},l)))}).pipe(Ht(ge))}function js(e,t){let{viewport$:r,target$:n,print$:o}=t;return R(...P(".annotate:not(.highlight)",e).map(i=>Es(i,{target$:n,print$:o})),...P("pre:not(.mermaid) > code",e).map(i=>Ls(i,{target$:n,print$:o})),...P("a",e).map(i=>Cs(i,t)),...P("pre.mermaid",e).map(i=>$s(i)),...P("table:not([class])",e).map(i=>Is(i)),...P("details",e).map(i=>Ms(i,{target$:n,print$:o})),...P("[data-tabs]",e).map(i=>Rs(i,{viewport$:r,target$:n})),...P("[title]:not([data-preview])",e).filter(()=>X("content.tooltips")).map(i=>Ge(i,{viewport$:r})),...P(".footnote-ref",e).filter(()=>X("content.footnote.tooltips")).map(i=>Rr(i,{content$:new U(a=>{let s=new URL(i.href).hash.slice(1),c=Array.from(document.getElementById(s).cloneNode(!0).children),l=On(...c);return a.next(l),document.body.append(l),()=>l.remove()}),viewport$:r})))}function dp(e,{alert$:t}){return t.pipe(g(r=>R(Y(!0),Y(!1).pipe(It(2e3))).pipe(f(n=>({message:r,active:n})))))}function Fs(e,t){let r=G(".md-typeset",e);return j(()=>{let n=new I;return n.subscribe(({message:o,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=o}),dp(e,t).pipe($(o=>n.next(o)),V(()=>n.complete()),f(o=>H({ref:e},o)))})}function hp({viewport$:e}){if(!X("header.autohide"))return Y(!1);let t=e.pipe(f(({offset:{y:o}})=>o),Pt(2,1),f(([o,i])=>[oMath.abs(i-o.y)>100),f(([,[o]])=>o),ie()),n=Tn("search");return re([e,n]).pipe(f(([{offset:o},i])=>o.y>400&&!i),ie(),g(o=>o?r:Y(!1)),J(!1))}function Us(e,t){return j(()=>re([Re(e),hp(t)])).pipe(f(([{height:r},n])=>({height:r,hidden:n})),ie((r,n)=>r.height===n.height&&r.hidden===n.hidden),se(1))}function Ns(e,{viewport$:t,header$:r,main$:n}){return j(()=>{let o=new I,i=o.pipe(he(),ye(!0));o.pipe(fe("active"),Ze(r)).subscribe(([{active:s},{hidden:c}])=>{e.classList.toggle("md-header--shadow",s&&!c),e.hidden=c});let a=me(P("[title]",e)).pipe(L(()=>X("content.tooltips")),oe(s=>Ge(s,{viewport$:t})));return n.subscribe(o),r.pipe(Q(i),f(s=>H({ref:e},s)),Rt(a.pipe(Q(i))))})}function vp(e,{viewport$:t,header$:r}){return Sn(e,{viewport$:t,header$:r}).pipe(f(({offset:{y:n}})=>{let{height:o}=Ae(e);return{active:o>0&&n>=o}}),fe("active"))}function Ds(e,t){return j(()=>{let r=new I;r.subscribe({next({active:o}){e.classList.toggle("md-header__title--active",o)},complete(){e.classList.remove("md-header__title--active")}});let n=Le(".md-content h1");return typeof n=="undefined"?y:vp(n,t).pipe($(o=>r.next(o)),V(()=>r.complete()),f(o=>H({ref:e},o)))})}function Ws(e,{viewport$:t,header$:r}){let n=r.pipe(f(({height:i})=>i),ie()),o=n.pipe(g(()=>Re(e).pipe(f(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),fe("bottom"))));return re([n,o,t]).pipe(f(([i,{top:a,bottom:s},{offset:{y:c},size:{height:l}}])=>(l=Math.max(0,l-Math.max(0,a-c,i)-Math.max(0,l+c-s)),{offset:a-i,height:l,active:a-i<=c})),ie((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function bp(e){let t=__md_get("__palette")||{index:e.findIndex(n=>matchMedia(n.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return Y(...e).pipe(oe(n=>b(n,"change").pipe(f(()=>n))),J(e[r]),f(n=>({index:e.indexOf(n),color:{media:n.getAttribute("data-md-color-media"),scheme:n.getAttribute("data-md-color-scheme"),primary:n.getAttribute("data-md-color-primary"),accent:n.getAttribute("data-md-color-accent")}})),se(1))}function Vs(e){let t=P("input",e),r=A("meta",{name:"theme-color"});document.head.appendChild(r);let n=A("meta",{name:"color-scheme"});document.head.appendChild(n);let o=Ir("(prefers-color-scheme: light)");return j(()=>{let i=new I;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),c=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=c.getAttribute("data-md-color-scheme"),a.color.primary=c.getAttribute("data-md-color-primary"),a.color.accent=c.getAttribute("data-md-color-accent")}for(let[s,c]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,c);for(let s=0;sa.key==="Enter"),pe(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(f(()=>{let a=ht("header"),s=window.getComputedStyle(a);return n.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(c=>(+c).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(Ie(ge)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),bp(t).pipe(Q(o.pipe(ke(1))),jt(),$(a=>i.next(a)),V(()=>i.complete()),f(a=>H({ref:e},a)))})}function zs(e,{progress$:t}){return j(()=>{let r=new I;return r.subscribe(({value:n})=>{e.style.setProperty("--md-progress-value",`${n}`)}),t.pipe($(n=>r.next({value:n})),V(()=>r.complete()),f(n=>({ref:e,value:n})))})}var qs='.v u{text-decoration:underline!important;text-decoration-style:wavy!important;text-decoration-thickness:1px!important}.p{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-backdrop)/var(--alpha-lighter));cursor:pointer;height:100%;pointer-events:auto;position:absolute;transition:opacity .25s;width:100%}.p.m{opacity:0;pointer-events:none;transition:opacity .35s}.r{align-items:center;background-color:initial;border:none;border-radius:var(--space-2);cursor:pointer;display:flex;flex-shrink:0;font-family:var(--font-family);height:36px;justify-content:center;outline:none;padding:0;position:relative;transition:background-color .25s,color .25s;width:36px;z-index:1}.r svg{stroke:rgb(var(--color-foreground));height:18px;opacity:.5;width:18px}.r:before{background-color:rgb(var(--color-background-subtle));border-radius:var(--border-radius-2);content:"";inset:0;opacity:0;position:absolute;transform:scale(.75);transition:transform 125ms,opacity 125ms;z-index:0}.r:hover:before{opacity:1;transform:scale(1)}.r.c{cursor:auto}.r.c:before{display:none}.n{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-background)/var(--alpha-light));border-radius:var(--space-3);box-shadow:0 0 60px #0000000d;display:flex;height:480px;overflow:hidden;pointer-events:auto;position:absolute;transition:transform .25s cubic-bezier(.16,1,.3,1),opacity .25s;width:640px}.n.l{opacity:0;pointer-events:none;transform:scale(1.1);transition:transform .25s .15s,opacity .15s}@media (max-width:680px){.n{border-radius:0;height:100%;width:100%}}.u{display:flex;flex-basis:min-content;flex-direction:column;flex-grow:1;flex-shrink:0}@keyframes d{0%{transform:scale(0)}50%{transform:scale(1.2)}to{transform:scale(1)}}.y{animation:d .25s ease-in-out;background:var(--color-highlight);border-radius:100%;color:#fff;font-size:8px;font-weight:700;height:12px;padding-top:1px;position:absolute;right:4px;top:4px;width:12px}.i{background-color:rgb(var(--color-background-subtle)/var(--alpha-lighter));flex-shrink:0;overflow:scroll;position:relative;transition:width .35s cubic-bezier(.16,1,.3,1),opacity .25s;width:200px}.i>*{transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.i.l{opacity:0;width:0}.i.l>*{transform:translate(-48px)}@media (max-width:680px){.i{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-background-subtle)/var(--alpha-light));box-shadow:0 0 60px #00000026;height:100%;position:absolute;right:0;top:0}}.w{border-bottom:1px solid rgb(var(--color-foreground)/var(--alpha-lightest));display:flex;gap:var(--space-1);padding:var(--space-2)}.k{-webkit-overflow-scrolling:touch;overflow:auto;overscroll-behavior:contain}.z{padding:8px 10px}.X{color:rgb(var(--color-foreground)/var(--alpha-light));padding:var(--space-2);position:absolute;width:200px}.X,.j{display:flex;flex-direction:column}.j{gap:2px;list-style:none;padding:0}.F,.j{margin:0}.F{font-size:16px;font-weight:400}.F,.I{padding:8px}.I{font-size:14px;margin:4px 0 0;opacity:.5}.I,.o{font-size:12px}.o{cursor:pointer;display:flex;padding:4px 8px;position:relative}.o:before{background-color:var(--color-highlight-transparent);border-radius:var(--space-1);content:"";inset:0;opacity:0;position:absolute;transform:scale(.75);transition:transform 125ms,opacity 125ms;z-index:0}.o.g:before,.o:hover:before{opacity:1;transform:scale(1)}.o.g,.o:hover{color:var(--color-highlight)}.R{flex-grow:1}.R,.q{position:relative}.q{font-weight:700}.f{flex-grow:1}.f input{background:#0000;border:none;color:rgb(var(--color-foreground));font-family:var(--font-family);font-size:16px;height:100%;letter-spacing:-.25px;outline:none;width:100%}.b{color:rgb(var(--color-foreground)/var(--alpha-light));display:flex;flex-direction:column;gap:2px;line-height:1.3;list-style:none;margin:var(--space-2);margin-top:0;padding:0}.A,.b li{margin:0}.A{color:rgb(var(--color-foreground)/var(--alpha-lighter));font-size:12px;margin-top:var(--space-2);padding:0 18px}.a{border-radius:var(--space-2);color:inherit;cursor:pointer;display:flex;flex-direction:row;flex-grow:1;padding:8px 10px;position:relative;text-decoration:none}.a:before{background-color:rgb(var(--color-background-subtle));border-radius:var(--border-radius-2);content:"";display:block;inset:0;opacity:0;position:absolute;transform:scale(.9);transition:transform 125ms,opacity 125ms;z-index:0}@media (pointer:fine){.a.h:before,.a:hover:before{opacity:1;transform:scale(1)}}.a mark{background:#0000;color:var(--color-highlight)}.a u{background-color:var(--color-highlight-transparent);border-radius:2px;box-shadow:0 0 0 1px var(--color-highlight-transparent);text-decoration:none}.B{flex-grow:1}.s{margin-right:-8px;opacity:0;position:relative;transform:translate(-2px);transition:transform 125ms,opacity 125ms;z-index:0}@media (pointer:fine){.h>.s,:hover>.s{opacity:1;transform:none}}.x{font-size:14px;margin:0;position:relative}.x code{background:rgb(var(--color-background-subtle));border-radius:var(--space-1);font-size:13px;padding:2px 4px}.t{color:rgb(var(--color-foreground)/var(--alpha-lighter));display:inline-flex;flex-wrap:wrap;font-size:12px;gap:var(--space-1);list-style:none;margin:0;padding:0;position:relative}.t li{white-space:nowrap}.t li:after{content:"/";display:inline;margin-left:var(--space-1)}.t li:last-child:after{content:"";display:none}.e{--space-1:4px;--space-2:calc(var(--space-1)*2);--space-3:calc(var(--space-2)*2);--space-4:calc(var(--space-3)*2);--space-5:calc(var(--space-4)*2);--alpha-light:.7;--alpha-lighter:.54;--alpha-lightest:.1;--color-highlight:var(--md-accent-fg-color,#526cfe);--color-highlight-transparent:var(--md-accent-fg-color--transparent,#526cfe1a);--border-radius-1:var(--space-1);--border-radius-2:var(--space-2);--border-radius-3:calc(var(--space-1) + var(--space-2));--font-family:var(--md-text-font-family,Inter,Roboto Flex,system-ui,sans-serif);--font-size:16px;--line-height:1.5;--letter-spacing:-.5px;-webkit-font-smoothing:antialiased;align-items:center;display:flex;font-family:var(--font-family);font-size:var(--font-size);height:100vh;justify-content:center;letter-spacing:var(--letter-spacing);line-height:var(--line-height);pointer-events:none;position:absolute;width:100vw}@media (pointer:coarse){.e{height:-webkit-fill-available}}.e *,.e :after,.e :before{box-sizing:border-box}';function Ks(e,{index$:t}){let r=Ue(),n=document.createElement("div");document.body.appendChild(n),n.style.position="fixed",n.style.height="100%",n.style.top="0",n.style.zIndex="4";let o=n.attachShadow({mode:"open"});o.appendChild(A("style",{},qs.toString()));try{Ya(r.search,{highlight:r.features.includes("search.highlight")}),me(t).subscribe(i=>{for(let a of i.items)a.location=new URL(a.location,r.base).toString();Ja(i,o)}),b(e,"click").subscribe(()=>{go()}),Tn("search").pipe(ke(1)).subscribe(()=>go())}catch(i){e.hidden=!0;let a=G("label[for=__search]");a.hidden=!0}return Ke}var Bs=_r(So());function Ys(e,{index$:t,location$:r}){return re([t,r.pipe(J(Ye()),L(n=>!!n.searchParams.get("h")))]).pipe(f(([n,o])=>_p(n.config)(o.searchParams.get("h"))),f(n=>{var a;let o=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let c=s.textContent,l=n(c);l.length>c.length&&o.set(s,l)}for(let[s,c]of o){let{childNodes:l}=A("span",null,c);s.replaceWith(...Array.from(l))}return{ref:e,nodes:o}}))}function _p(e){let t=e.separator.split("|").map(o=>o.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":o).join("|"),r=new RegExp(t,"img"),n=(o,i,a)=>`${i}${a}`;return o=>{o=o.replace(/\s+/g," ").replace(/&/g,"&").trim();let i=new RegExp(`(^|${e.separator}|)(${o.split(r).map(a=>a.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&")).filter(a=>a.length>0).join("|")})`,"img");return a=>(0,Bs.default)(a).replace(i,n).replace(/<\/mark>(\s+)]*>/img,"$1")}}function yp(e,{viewport$:t,main$:r}){let n=e.closest(".md-grid"),o=n.offsetTop-n.parentElement.offsetTop;return re([r,t]).pipe(f(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(o,Math.max(0,s-i))-o,{height:a,locked:s>=i+o})),ie((i,a)=>i.height===a.height&&i.locked===a.locked))}function Ao(e,n){var o=n,{header$:t}=o,r=gr(o,["header$"]);let i=G(".md-sidebar__scrollwrap",e),{y:a}=wt(i);return j(()=>{let s=new I,c=s.pipe(he(),ye(!0)),l=s.pipe(Xe(0,je));return l.pipe(pe(t)).subscribe({next([{height:u},{height:p}]){i.style.height=`${u-2*a}px`,e.style.top=`${p}px`},complete(){i.style.height="",e.style.top=""}}),l.pipe(Sr()).subscribe(()=>{for(let u of P(".md-nav__link--active[href]",e)){if(!u.clientHeight)continue;let p=u.closest(".md-sidebar__scrollwrap");if(typeof p!="undefined"){let d=u.offsetTop-p.offsetTop,{height:m}=Ae(p);p.scrollTo({top:d-m/2})}}}),me(P("label[tabindex]",e)).pipe(oe(u=>b(u,"click").pipe(Ie(ge),f(()=>u),Q(c)))).subscribe(u=>{let p=G(`[id="${u.htmlFor}"]`);G(`[aria-labelledby="${u.id}"]`).setAttribute("aria-expanded",`${p.checked}`)}),X("content.tooltips")&&me(P("abbr[title]",e)).pipe(oe(u=>Ge(u,{viewport$})),Q(c)).subscribe(),yp(e,r).pipe($(u=>s.next(u)),V(()=>s.complete()),f(u=>H({ref:e},u)))})}function Gs(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return $t(et(`${r}/releases/latest`).pipe(_e(()=>y),f(n=>({version:n.tag_name})),ot({})),et(r).pipe(_e(()=>y),f(n=>({stars:n.stargazers_count,forks:n.forks_count})),ot({}))).pipe(f(([n,o])=>H(H({},n),o)))}else{let r=`https://api.github.com/users/${e}`;return et(r).pipe(f(n=>({repositories:n.public_repos})),ot({}))}}function Js(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return $t(et(`${r}/releases/permalink/latest`).pipe(_e(()=>y),f(({tag_name:n})=>({version:n})),ot({})),et(r).pipe(_e(()=>y),f(({star_count:n,forks_count:o})=>({stars:n,forks:o})),ot({}))).pipe(f(([n,o])=>H(H({},n),o)))}function Xs(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,n]=t;return Gs(r,n)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,n]=t;return Js(r,n)}return y}var xp;function wp(e){return xp||(xp=j(()=>{let t=__md_get("__source",sessionStorage);if(t)return Y(t);if(Ee("consent").length){let n=__md_get("__consent");if(!(n&&n.github))return y}return Xs(e.href).pipe($(n=>__md_set("__source",n,sessionStorage)))}).pipe(_e(()=>y),L(t=>Object.keys(t).length>0),f(t=>({facts:t})),se(1)))}function Zs(e){let t=G(":scope > :last-child",e);return j(()=>{let r=new I;return r.subscribe(({facts:n})=>{t.appendChild(bs(n)),t.classList.add("md-source__repository--active")}),wp(e).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}function Ep(e,{viewport$:t,header$:r}){return Re(document.body).pipe(g(()=>Sn(e,{header$:r,viewport$:t})),f(({offset:{y:n}})=>({hidden:n>=10})),fe("hidden"))}function Qs(e,t){return j(()=>{let r=new I;return r.subscribe({next({hidden:n}){e.hidden=n},complete(){e.hidden=!1}}),(X("navigation.tabs.sticky")?Y({hidden:!1}):Ep(e,t)).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}function Tp(e,{viewport$:t,header$:r}){let n=new Map,o=P(".md-nav__link",e);for(let s of o){let c=decodeURIComponent(s.hash.substring(1)),l=Le(`[id="${c}"]`);typeof l!="undefined"&&n.set(s,l)}let i=r.pipe(fe("height"),f(({height:s})=>{let c=ht("main"),l=G(":scope > :first-child",c);return s+.9*(l.offsetTop-c.offsetTop)}),xe());return Re(document.body).pipe(fe("height"),g(s=>j(()=>{let c=[];return Y([...n].reduce((l,[u,p])=>{for(;c.length&&n.get(c[c.length-1]).tagName>=p.tagName;)c.pop();let d=p.offsetTop;for(;!d&&p.parentElement;)p=p.parentElement,d=p.offsetTop;let m=p.offsetParent;for(;m;m=m.offsetParent)d+=m.offsetTop;return l.set([...c=[...c,u]].reverse(),d)},new Map))}).pipe(f(c=>new Map([...c].sort(([,l],[,u])=>l-u))),Ze(i),g(([c,l])=>t.pipe(Or(([u,p],{offset:{y:d},size:m})=>{let h=d+m.height>=Math.floor(s.height);for(;p.length;){let[,v]=p[0];if(v-l=d&&!h)p=[u.pop(),...p];else break}return[u,p]},[[],[...c]]),ie((u,p)=>u[0]===p[0]&&u[1]===p[1])))))).pipe(f(([s,c])=>({prev:s.map(([l])=>l),next:c.map(([l])=>l)})),J({prev:[],next:[]}),Pt(2,1),f(([s,c])=>s.prev.length{let i=new I,a=i.pipe(he(),ye(!0));if(i.subscribe(({prev:s,next:c})=>{for(let[l]of c)l.classList.remove("md-nav__link--passed"),l.classList.remove("md-nav__link--active");for(let[l,[u]]of s.entries())u.classList.add("md-nav__link--passed"),u.classList.toggle("md-nav__link--active",l===s.length-1)}),X("toc.follow")){let s=R(t.pipe(Be(1),f(()=>{})),t.pipe(Be(250),f(()=>"smooth")));i.pipe(L(({prev:c})=>c.length>0),Ze(n.pipe(Ie(ge))),pe(s)).subscribe(([[{prev:c}],l])=>{let[u]=c[c.length-1];if(u.offsetHeight){let p=ki(u);if(typeof p!="undefined"){let d=u.offsetTop-p.offsetTop,{height:m}=Ae(p);p.scrollTo({top:d-m/2,behavior:l})}}})}return X("navigation.tracking")&&t.pipe(Q(a),fe("offset"),Be(250),ke(1),Q(o.pipe(ke(1))),jt({delay:250}),pe(i)).subscribe(([,{prev:s}])=>{let c=Ye(),l=s[s.length-1];if(l&&l.length){let[u]=l,{hash:p}=new URL(u.href);c.hash!==p&&(c.hash=p,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),Tp(e,{viewport$:t,header$:r}).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))})}function Sp(e,{viewport$:t,main$:r,target$:n}){let o=t.pipe(f(({offset:{y:a}})=>a),Pt(2,1),f(([a,s])=>a>s&&s>0),ie()),i=r.pipe(f(({active:a})=>a));return re([i,o]).pipe(f(([a,s])=>!(a&&s)),ie(),Q(n.pipe(ke(1))),ye(!0),jt({delay:250}),f(a=>({hidden:a})))}function tc(e,{viewport$:t,header$:r,main$:n,target$:o}){let i=new I,a=i.pipe(he(),ye(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(Q(a),fe("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),b(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),Sp(e,{viewport$:t,main$:n,target$:o}).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))}function rc(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,t.port&&(e.port=t.port),e}function Op(e,t){let r=new Map;for(let n of P("url",e)){let o=G("loc",n),i=[rc(new URL(o.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",n)){let s=a.getAttribute("href");s!=null&&i.push(rc(new URL(s),t))}}return r}function dr(e){return ns(new URL("sitemap.xml",e)).pipe(f(t=>Op(t,new URL(e))),_e(()=>Y(new Map)),xe())}function nc({document$:e}){let t=new Map;e.pipe(g(()=>P("link[rel=alternate]")),f(r=>new URL(r.href)),L(r=>!t.has(r.toString())),oe(r=>dr(r).pipe(f(n=>[r,n]),_e(()=>y)))).subscribe(([r,n])=>{t.set(r.toString().replace(/\/$/,""),n)}),b(document.body,"click").pipe(L(r=>!r.metaKey&&!r.ctrlKey),g(r=>{if(r.target instanceof Element){let n=r.target.closest("a");if(n&&!n.target){let o=[...t].find(([p])=>n.href.startsWith(`${p}/`));if(typeof o=="undefined")return y;let[i,a]=o,s=Ye();if(s.href.startsWith(i))return y;let c=Ue(),l=s.href.replace(c.base,"");l=`${i}/${l}`;let u=a.has(l.split("#")[0])?new URL(l,c.base):new URL(i);return r.preventDefault(),Y(u)}}return y})).subscribe(r=>dt(r,!0))}var Co=_r(Mo());function Lp(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function oc({alert$:e}){Co.default.isSupported()&&new U(t=>{new Co.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||Lp(G(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe($(t=>{t.trigger.focus()}),f(()=>Bt("clipboard.copied"))).subscribe(e)}function ic(e,t){if(!(e.target instanceof Element))return y;let r=e.target.closest("a");if(r===null)return y;if(r.target||e.metaKey||e.ctrlKey)return y;let n=new URL(r.href);return n.search=n.hash="",t.has(`${n}`)?(e.preventDefault(),Y(r)):y}function ac(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function sc(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let n=t.getAttribute(r);if(n&&!/^(?:[a-z]+:)?\/\//i.test(n)){t[r]=t[r];break}}return Y(e)}function Mp(e){for(let n of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...X("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let o=Le(n),i=Le(n,e);typeof o!="undefined"&&typeof i!="undefined"&&o.replaceWith(i)}let t=ac(document);for(let[n,o]of ac(e))t.has(n)?t.delete(n):document.head.appendChild(o);for(let n of t.values()){let o=n.getAttribute("name");o!=="theme-color"&&o!=="color-scheme"&&n.remove()}let r=ht("container");return nt(P("script",r)).pipe(g(n=>{let o=e.createElement("script");if(n.src){for(let i of n.getAttributeNames())o.setAttribute(i,n.getAttribute(i));return n.replaceWith(o),new U(i=>{o.onload=()=>i.complete()})}else return o.textContent=n.textContent,n.replaceWith(o),y}),he(),ye(document))}function cc({sitemap$:e,location$:t,viewport$:r,progress$:n}){if(location.protocol==="file:")return Ke;Y(document).subscribe(sc);let o=b(document.body,"click").pipe(Ze(e),g(([s,c])=>ic(s,c)),f(({href:s})=>new URL(s)),xe()),i=b(window,"popstate").pipe(f(Ye),xe());o.pipe(pe(r)).subscribe(([s,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",s)}),R(o,i).subscribe(t);let a=t.pipe(fe("pathname"),g(s=>En(s,{progress$:n}).pipe(_e(()=>(dt(s,!0),y)))),g(sc),g(Mp),xe());return R(a.pipe(pe(t,(s,c)=>c)),a.pipe(g(()=>t),fe("hash")),t.pipe(ie((s,c)=>s.pathname===c.pathname&&s.hash===c.hash),g(()=>o),$(()=>history.back()))).subscribe(s=>{var c,l;history.state!==null||!s.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",es(s.hash),history.scrollRestoration="manual")}),t.subscribe(()=>{history.scrollRestoration="manual"}),b(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),r.pipe(fe("offset"),Be(100)).subscribe(({offset:s})=>{history.replaceState(s,"")}),X("navigation.instant.prefetch")&&R(b(document.body,"mousemove"),b(document.body,"focusin")).pipe(Ze(e),g(([s,c])=>ic(s,c)),Be(25),Yn(({href:s})=>s),cn(s=>{let c=document.createElement("link");return c.rel="prefetch",c.href=s.toString(),document.head.appendChild(c),b(c,"load").pipe(f(()=>c),Me(1))})).subscribe(s=>s.remove()),a}function lc(e){var u;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:n,currentBaseURL:o}=e,i=(u=Ho(o))==null?void 0:u.pathname;if(i===void 0)return;let a=kp(n.pathname,i);if(a===void 0)return;let s=Cp(t.keys());if(!t.has(s))return;let c=Ho(a,s);if(!c||!t.has(c.href))return;let l=Ho(a,r);if(l)return l.hash=n.hash,l.search=n.search,l}function Ho(e,t){try{return new URL(e,t)}catch(r){return}}function kp(e,t){if(e.startsWith(t))return e.slice(t.length)}function Ap(e,t){let r=Math.min(e.length,t.length),n;for(n=0;ny)),n=r.pipe(f(o=>{let[,i]=t.base.match(/([^/]+)\/?$/);return o.find(({version:a,aliases:s})=>a===i||s.includes(i))||o[0]}));r.pipe(f(o=>new Map(o.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),g(o=>b(document.body,"click").pipe(L(i=>!i.metaKey&&!i.ctrlKey),pe(n),g(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&o.has(s.href)){let c=s.href;return!i.target.closest(".md-version")&&o.get(c)===a?y:(i.preventDefault(),Y(new URL(c)))}}return y}),g(i=>dr(i).pipe(f(a=>{var s;return(s=lc({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:Ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(o=>dt(o,!0)),re([r,n]).subscribe(([o,i])=>{G(".md-header__topic").appendChild(_s(o,i))}),e.pipe(g(()=>n)).subscribe(o=>{var s;let i=new URL(t.base),a=__md_get("__outdated",sessionStorage,i);if(a===null){a=!0;let c=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(c)||(c=[c]);e:for(let l of c)for(let u of o.aliases.concat(o.version))if(new RegExp(l,"i").test(u)){a=!1;break e}__md_set("__outdated",a,sessionStorage,i)}if(a)for(let c of Ee("outdated"))c.hidden=!1})}function pc({document$:e,viewport$:t}){e.pipe(g(()=>P(".md-ellipsis")),oe(r=>Et(r).pipe(Q(e.pipe(ke(1))),L(n=>n),f(()=>r),Me(1))),L(r=>r.offsetWidth{let n=r.innerText,o=r.closest("a")||r;return o.title=n,X("content.tooltips")?Ge(o,{viewport$:t}).pipe(Q(e.pipe(ke(1))),V(()=>o.removeAttribute("title"))):y})).subscribe(),X("content.tooltips")&&e.pipe(g(()=>P(".md-status")),oe(r=>Ge(r,{viewport$:t}))).subscribe()}function fc({document$:e,tablet$:t}){e.pipe(g(()=>P(".md-toggle--indeterminate")),$(r=>{r.indeterminate=!0,r.checked=!1}),oe(r=>b(r,"change").pipe(Xn(()=>r.classList.contains("md-toggle--indeterminate")),f(()=>r))),pe(t)).subscribe(([r,n])=>{r.classList.remove("md-toggle--indeterminate"),n&&(r.checked=!1)})}function Hp(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function mc({document$:e}){e.pipe(g(()=>P("[data-md-scrollfix]")),$(t=>t.removeAttribute("data-md-scrollfix")),L(Hp),oe(t=>b(t,"touchstart").pipe(f(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let n=e[r];typeof n=="string"?n=document.createTextNode(n):n.parentNode&&n.parentNode.removeChild(n),r?t.insertBefore(this.previousSibling,n):t.replaceChild(n,this)}}}));function $p(){return location.protocol==="file:"?ar(`${new URL("search.js",Mn.base)}`).pipe(f(()=>__index),_e(()=>Ke),se(1)):et(new URL("search.json",Mn.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var vt=Si(),Ur=Za(),hr=ts(Ur),hc=Xa(),ze=cs(),$o=Ir("(min-width: 60em)"),vc=Ir("(min-width: 76.25em)"),bc=rs(),Mn=Ue(),gc=Le(".md-search")?$p():Ke,Po=new I;oc({alert$:Po});nc({document$:vt});var Io=new I,_c=dr(Mn.base);X("navigation.instant")&&cc({sitemap$:_c,location$:Ur,viewport$:ze,progress$:Io}).subscribe(vt);var dc;((dc=Mn.version)==null?void 0:dc.provider)==="mike"&&uc({document$:vt});R(Ur,hr).pipe(It(125)).subscribe(()=>{Eo("drawer",!1),Eo("search",!1)});hc.pipe(L(({mode:e,meta:t})=>e==="global"&&!t)).subscribe(e=>{switch(e.type){case",":case"p":let t=document.querySelector("link[rel=prev]");t instanceof HTMLLinkElement&&dt(t);break;case".":case"n":let r=document.querySelector("link[rel=next]");r instanceof HTMLLinkElement&&dt(r);break;case"/":let n=document.querySelector("[data-md-component=search] button");n instanceof HTMLButtonElement&&n.click();break;case"Enter":let o=xt();o instanceof HTMLLabelElement&&o.click()}});pc({viewport$:ze,document$:vt});fc({document$:vt,tablet$:$o});mc({document$:vt});var Lt=Us(ht("header"),{viewport$:ze}),Fr=vt.pipe(f(()=>ht("main")),g(e=>Ws(e,{viewport$:ze,header$:Lt})),se(1)),Pp=R(...Ee("consent").map(e=>us(e,{target$:hr})),...Ee("dialog").map(e=>Fs(e,{alert$:Po})),...Ee("palette").map(e=>Vs(e)),...Ee("progress").map(e=>zs(e,{progress$:Io})),...Ee("search").map(e=>Ks(e,{index$:gc})),...Ee("source").map(e=>Zs(e))),Ip=j(()=>R(...Ee("announce").map(e=>ls(e)),...Ee("content").map(e=>js(e,{sitemap$:_c,viewport$:ze,target$:hr,print$:bc})),...Ee("content").map(e=>X("search.highlight")?Ys(e,{index$:gc,location$:Ur}):y),...Ee("header").map(e=>Ns(e,{viewport$:ze,header$:Lt,main$:Fr})),...Ee("header-title").map(e=>Ds(e,{viewport$:ze,header$:Lt})),...Ee("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?yo(vc,()=>Ao(e,{viewport$:ze,header$:Lt,main$:Fr})):yo($o,()=>Ao(e,{viewport$:ze,header$:Lt,main$:Fr}))),...Ee("tabs").map(e=>Qs(e,{viewport$:ze,header$:Lt})),...Ee("toc").map(e=>ec(e,{viewport$:ze,header$:Lt,main$:Fr,target$:hr})),...Ee("top").map(e=>tc(e,{viewport$:ze,header$:Lt,main$:Fr,target$:hr})))),yc=vt.pipe(g(()=>Ip),Rt(Pp),se(1));yc.subscribe();window.document$=vt;window.location$=Ur;window.target$=hr;window.keyboard$=hc;window.viewport$=ze;window.tablet$=$o;window.screen$=vc;window.print$=bc;window.alert$=Po;window.progress$=Io;window.component$=yc;})(); -/*! update cache: 20260331044451 */ +/*! update cache: 20260331053143 */ diff --git a/en/assets/javascripts/bundle.c2b142ea.min.js b/en/assets/javascripts/bundle.c2b142ea.min.js index c88793c85..7f19f1463 100644 --- a/en/assets/javascripts/bundle.c2b142ea.min.js +++ b/en/assets/javascripts/bundle.c2b142ea.min.js @@ -1,4 +1,4 @@ "use strict";(()=>{var xc=Object.create;var kn=Object.defineProperty,wc=Object.defineProperties,Ec=Object.getOwnPropertyDescriptor,Tc=Object.getOwnPropertyDescriptors,Sc=Object.getOwnPropertyNames,Dr=Object.getOwnPropertySymbols,Oc=Object.getPrototypeOf,An=Object.prototype.hasOwnProperty,Fo=Object.prototype.propertyIsEnumerable;var jo=(e,t,r)=>t in e?kn(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,H=(e,t)=>{for(var r in t||(t={}))An.call(t,r)&&jo(e,r,t[r]);if(Dr)for(var r of Dr(t))Fo.call(t,r)&&jo(e,r,t[r]);return e},He=(e,t)=>wc(e,Tc(t));var gr=(e,t)=>{var r={};for(var n in e)An.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&Dr)for(var n of Dr(e))t.indexOf(n)<0&&Fo.call(e,n)&&(r[n]=e[n]);return r};var Cn=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Lc=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Sc(t))!An.call(e,o)&&o!==r&&kn(e,o,{get:()=>t[o],enumerable:!(n=Ec(t,o))||n.enumerable});return e};var _r=(e,t,r)=>(r=e!=null?xc(Oc(e)):{},Lc(t||!e||!e.__esModule?kn(r,"default",{value:e,enumerable:!0}):r,e));var Uo=(e,t,r)=>new Promise((n,o)=>{var i=c=>{try{s(r.next(c))}catch(l){o(l)}},a=c=>{try{s(r.throw(c))}catch(l){o(l)}},s=c=>c.done?n(c.value):Promise.resolve(c.value).then(i,a);s((r=r.apply(e,t)).next())});var Do=Cn((Hn,No)=>{(function(e,t){typeof Hn=="object"&&typeof No!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Hn,(function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(_){return!!(_&&_!==document&&_.nodeName!=="HTML"&&_.nodeName!=="BODY"&&"classList"in _&&"contains"in _.classList)}function c(_){var de=_.type,be=_.tagName;return!!(be==="INPUT"&&a[de]&&!_.readOnly||be==="TEXTAREA"&&!_.readOnly||_.isContentEditable)}function l(_){_.classList.contains("focus-visible")||(_.classList.add("focus-visible"),_.setAttribute("data-focus-visible-added",""))}function u(_){_.hasAttribute("data-focus-visible-added")&&(_.classList.remove("focus-visible"),_.removeAttribute("data-focus-visible-added"))}function p(_){_.metaKey||_.altKey||_.ctrlKey||(s(r.activeElement)&&l(r.activeElement),n=!0)}function d(_){n=!1}function m(_){s(_.target)&&(n||c(_.target))&&l(_.target)}function h(_){s(_.target)&&(_.target.classList.contains("focus-visible")||_.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(_.target))}function v(_){document.visibilityState==="hidden"&&(o&&(n=!0),x())}function x(){document.addEventListener("mousemove",E),document.addEventListener("mousedown",E),document.addEventListener("mouseup",E),document.addEventListener("pointermove",E),document.addEventListener("pointerdown",E),document.addEventListener("pointerup",E),document.addEventListener("touchmove",E),document.addEventListener("touchstart",E),document.addEventListener("touchend",E)}function w(){document.removeEventListener("mousemove",E),document.removeEventListener("mousedown",E),document.removeEventListener("mouseup",E),document.removeEventListener("pointermove",E),document.removeEventListener("pointerdown",E),document.removeEventListener("pointerup",E),document.removeEventListener("touchmove",E),document.removeEventListener("touchstart",E),document.removeEventListener("touchend",E)}function E(_){_.target.nodeName&&_.target.nodeName.toLowerCase()==="html"||(n=!1,w())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",d,!0),document.addEventListener("pointerdown",d,!0),document.addEventListener("touchstart",d,!0),document.addEventListener("visibilitychange",v,!0),x(),r.addEventListener("focus",m,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)}))});var So=Cn((M0,vs)=>{"use strict";var Gu=/["'&<>]/;vs.exports=Ju;function Ju(e){var t=""+e,r=Gu.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i{(function(t,r){typeof jr=="object"&&typeof Lo=="object"?Lo.exports=r():typeof define=="function"&&define.amd?define([],r):typeof jr=="object"?jr.ClipboardJS=r():t.ClipboardJS=r()})(jr,function(){return(function(){var e={686:(function(n,o,i){"use strict";i.d(o,{default:function(){return vr}});var a=i(279),s=i.n(a),c=i(370),l=i.n(c),u=i(817),p=i.n(u);function d(B){try{return document.execCommand(B)}catch(C){return!1}}var m=function(C){var k=p()(C);return d("cut"),k},h=m;function v(B){var C=document.documentElement.getAttribute("dir")==="rtl",k=document.createElement("textarea");k.style.fontSize="12pt",k.style.border="0",k.style.padding="0",k.style.margin="0",k.style.position="absolute",k.style[C?"right":"left"]="-9999px";var D=window.pageYOffset||document.documentElement.scrollTop;return k.style.top="".concat(D,"px"),k.setAttribute("readonly",""),k.value=B,k}var x=function(C,k){var D=v(C);k.container.appendChild(D);var W=p()(D);return d("copy"),D.remove(),W},w=function(C){var k=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},D="";return typeof C=="string"?D=x(C,k):C instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(C==null?void 0:C.type)?D=x(C.value,k):(D=p()(C),d("copy")),D},E=w;function _(B){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?_=function(k){return typeof k}:_=function(k){return k&&typeof Symbol=="function"&&k.constructor===Symbol&&k!==Symbol.prototype?"symbol":typeof k},_(B)}var de=function(){var C=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},k=C.action,D=k===void 0?"copy":k,W=C.container,Z=C.target,We=C.text;if(D!=="copy"&&D!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Z!==void 0)if(Z&&_(Z)==="object"&&Z.nodeType===1){if(D==="copy"&&Z.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(D==="cut"&&(Z.hasAttribute("readonly")||Z.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(We)return E(We,{container:W});if(Z)return D==="cut"?h(Z):E(Z,{container:W})},be=de;function M(B){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?M=function(k){return typeof k}:M=function(k){return k&&typeof Symbol=="function"&&k.constructor===Symbol&&k!==Symbol.prototype?"symbol":typeof k},M(B)}function O(B,C){if(!(B instanceof C))throw new TypeError("Cannot call a class as a function")}function N(B,C){for(var k=0;k0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof W.action=="function"?W.action:this.defaultAction,this.target=typeof W.target=="function"?W.target:this.defaultTarget,this.text=typeof W.text=="function"?W.text:this.defaultText,this.container=M(W.container)==="object"?W.container:document.body}},{key:"listenClick",value:function(W){var Z=this;this.listener=l()(W,"click",function(We){return Z.onClick(We)})}},{key:"onClick",value:function(W){var Z=W.delegateTarget||W.currentTarget,We=this.action(Z)||"copy",Gt=be({action:We,container:this.container,target:this.target(Z),text:this.text(Z)});this.emit(Gt?"success":"error",{action:We,text:Gt,trigger:Z,clearSelection:function(){Z&&Z.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(W){return Yt("action",W)}},{key:"defaultTarget",value:function(W){var Z=Yt("target",W);if(Z)return document.querySelector(Z)}},{key:"defaultText",value:function(W){return Yt("text",W)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(W){var Z=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return E(W,Z)}},{key:"cut",value:function(W){return h(W)}},{key:"isSupported",value:function(){var W=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Z=typeof W=="string"?[W]:W,We=!!document.queryCommandSupported;return Z.forEach(function(Gt){We=We&&!!document.queryCommandSupported(Gt)}),We}}]),k})(s()),vr=Mt}),828:(function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,c){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(c))return s;s=s.parentNode}}n.exports=a}),438:(function(n,o,i){var a=i(828);function s(u,p,d,m,h){var v=l.apply(this,arguments);return u.addEventListener(d,v,h),{destroy:function(){u.removeEventListener(d,v,h)}}}function c(u,p,d,m,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof d=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return s(v,p,d,m,h)}))}function l(u,p,d,m){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&m.call(u,h)}}n.exports=c}),879:(function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}}),370:(function(n,o,i){var a=i(879),s=i(438);function c(d,m,h){if(!d&&!m&&!h)throw new Error("Missing required arguments");if(!a.string(m))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(d))return l(d,m,h);if(a.nodeList(d))return u(d,m,h);if(a.string(d))return p(d,m,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function l(d,m,h){return d.addEventListener(m,h),{destroy:function(){d.removeEventListener(m,h)}}}function u(d,m,h){return Array.prototype.forEach.call(d,function(v){v.addEventListener(m,h)}),{destroy:function(){Array.prototype.forEach.call(d,function(v){v.removeEventListener(m,h)})}}}function p(d,m,h){return s(document.body,d,m,h)}n.exports=c}),817:(function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),l=document.createRange();l.selectNodeContents(i),c.removeAllRanges(),c.addRange(l),a=c.toString()}return a}n.exports=o}),279:(function(n){function o(){}o.prototype={on:function(i,a,s){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var c=this;function l(){c.off(i,l),a.apply(s,arguments)}return l._=a,this.on(i,l,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),c=0,l=s.length;for(c;c0&&i[i.length-1])&&(l[0]===6||l[0]===2)){r=0;continue}if(l[0]===3&&(!i||l[1]>i[0]&&l[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function te(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(s){a={error:s}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function ne(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||c(m,v)})},h&&(o[m]=h(o[m])))}function c(m,h){try{l(n[m](h))}catch(v){d(i[0][3],v)}}function l(m){m.value instanceof kt?Promise.resolve(m.value.v).then(u,p):d(i[0][2],m)}function u(m){c("next",m)}function p(m){c("throw",m)}function d(m,h){m(h),i.shift(),i.length&&c(i[0][0],i[0][1])}}function zo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof $e=="function"?$e(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(s,c){a=e[i](a),o(s,c,a.done,a.value)})}}function o(i,a,s,c){Promise.resolve(c).then(function(l){i({value:l,done:s})},a)}}function F(e){return typeof e=="function"}function Jt(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Vr=Jt(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: `+r.map(function(n,o){return o+1+") "+n.toString()}).join(` `):"",this.name="UnsubscriptionError",this.errors=r}});function ct(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var rt=(function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=$e(a),c=s.next();!c.done;c=s.next()){var l=c.value;l.remove(this)}}catch(v){t={error:v}}finally{try{c&&!c.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var u=this.initialTeardown;if(F(u))try{u()}catch(v){i=v instanceof Vr?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var d=$e(p),m=d.next();!m.done;m=d.next()){var h=m.value;try{qo(h)}catch(v){i=i!=null?i:[],v instanceof Vr?i=ne(ne([],te(i)),te(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{m&&!m.done&&(o=d.return)&&o.call(d)}finally{if(n)throw n.error}}}if(i)throw new Vr(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)qo(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&ct(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&ct(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=(function(){var t=new e;return t.closed=!0,t})(),e})();var Pn=rt.EMPTY;function zr(e){return e instanceof rt||e&&"closed"in e&&F(e.remove)&&F(e.add)&&F(e.unsubscribe)}function qo(e){F(e)?e():e.unsubscribe()}var Je={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var Xt={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Pn:(this.currentObservers=null,s.push(r),new rt(function(){n.currentObservers=null,ct(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new U;return r.source=this,r},t.create=function(r,n){return new Qo(r,n)},t})(U);var Qo=(function(e){ue(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Pn},t})(I);var Un=(function(e){ue(t,e);function t(r){var n=e.call(this)||this;return n._value=r,n}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var n=e.prototype._subscribe.call(this,r);return!n.closed&&r.next(this._value),n},t.prototype.getValue=function(){var r=this,n=r.hasError,o=r.thrownError,i=r._value;if(n)throw o;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t})(I);var xr={now:function(){return(xr.delegate||Date).now()},delegate:void 0};var wr=(function(e){ue(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=xr);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,c=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),c=0;c0?e.prototype.schedule.call(this,r,n):(this.delay=n,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,n){return n>0||this.closed?e.prototype.execute.call(this,r,n):this._execute(r,n)},t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!=null&&o>0||o==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.flush(this),0)},t})(tr);var ri=(function(e){ue(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t})(rr);var Wn=new ri(ti);var ni=(function(e){ue(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o}return t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!==null&&o>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=er.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var a=r.actions;n!=null&&n===r._scheduled&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==n&&(er.cancelAnimationFrame(n),r._scheduled=void 0)},t})(tr);var oi=(function(e){ue(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n;r?n=r.id:(n=this._scheduled,this._scheduled=void 0);var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t})(rr);var je=new oi(ni);var y=new U(function(e){return e.complete()});function Br(e){return e&&F(e.schedule)}function Vn(e){return e[e.length-1]}function _t(e){return F(Vn(e))?e.pop():void 0}function qe(e){return Br(Vn(e))?e.pop():void 0}function Yr(e,t){return typeof Vn(e)=="number"?e.pop():t}var nr=(function(e){return e&&typeof e.length=="number"&&typeof e!="function"});function Gr(e){return F(e==null?void 0:e.then)}function Jr(e){return F(e[Qt])}function Xr(e){return Symbol.asyncIterator&&F(e==null?void 0:e[Symbol.asyncIterator])}function Zr(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Rc(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qr=Rc();function en(e){return F(e==null?void 0:e[Qr])}function tn(e){return Vo(this,arguments,function(){var r,n,o,i;return Wr(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,kt(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,kt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,kt(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function rn(e){return F(e==null?void 0:e.getReader)}function q(e){if(e instanceof U)return e;if(e!=null){if(Jr(e))return jc(e);if(nr(e))return Fc(e);if(Gr(e))return Uc(e);if(Xr(e))return ii(e);if(en(e))return Nc(e);if(rn(e))return Dc(e)}throw Zr(e)}function jc(e){return new U(function(t){var r=e[Qt]();if(F(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Fc(e){return new U(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?L(function(o,i){return e(o,i,n)}):Oe,Me(1),r?ot(t):wi(function(){return new on}))}}function Gn(e){return e<=0?function(){return y}:S(function(t,r){var n=[];t.subscribe(T(r,function(o){n.push(o),e=2,!0))}function xe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new I}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,c=s===void 0?!0:s;return function(l){var u,p,d,m=0,h=!1,v=!1,x=function(){p==null||p.unsubscribe(),p=void 0},w=function(){x(),u=d=void 0,h=v=!1},E=function(){var _=u;w(),_==null||_.unsubscribe()};return S(function(_,de){m++,!v&&!h&&x();var be=d=d!=null?d:r();de.add(function(){m--,m===0&&!v&&!h&&(p=Jn(E,c))}),be.subscribe(de),!u&&m>0&&(u=new Ct({next:function(M){return be.next(M)},error:function(M){v=!0,x(),p=Jn(w,o,M),be.error(M)},complete:function(){h=!0,x(),p=Jn(w,a),be.complete()}}),q(_).subscribe(u))})(l)}}function Jn(e,t){for(var r=[],n=2;ne.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function G(e,t=document){let r=Le(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function Le(e,t=document){return t.querySelector(e)||void 0}function xt(){var e,t,r,n;return(n=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?n:void 0}var il=R(b(document.body,"focusin"),b(document.body,"focusout")).pipe(Be(1),J(void 0),f(()=>xt()||document.body),se(1));function ir(e){return il.pipe(f(t=>e.contains(t)),ie())}function Ft(e,t){let{matches:r}=matchMedia("(hover)");return j(()=>(r?R(b(e,"mouseenter").pipe(f(()=>!0)),b(e,"mouseleave").pipe(f(()=>!1))):R(b(e,"touchstart").pipe(f(()=>!0)),b(e,"touchend").pipe(f(()=>!1)),b(e,"touchcancel").pipe(f(()=>!1)))).pipe(t?Tr(o=>Ve(+!o*t)):Oe,J(!0,e.matches(":hover"))))}function Oi(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Oi(e,r)}function A(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)Oi(n,o);return n}function Li(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function ar(e){let t=A("script",{src:e});return j(()=>(document.head.appendChild(t),R(b(t,"load"),b(t,"error").pipe(g(()=>zn(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(f(()=>{}),V(()=>document.head.removeChild(t)),Me(1))))}var Mi=new I,al=j(()=>typeof ResizeObserver=="undefined"?ar("https://unpkg.com/resize-observer-polyfill"):Y(void 0)).pipe(f(()=>new ResizeObserver(e=>e.forEach(t=>Mi.next(t)))),g(e=>R(Ke,Y(e)).pipe(V(()=>e.disconnect()))),se(1));function Ae(e){return{width:e.offsetWidth,height:e.offsetHeight}}function Re(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return al.pipe($(r=>r.observe(t)),g(r=>Mi.pipe(L(n=>n.target===t),V(()=>r.unobserve(t)))),f(()=>Ae(e)),J(Ae(e)))}function Mr(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ki(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Ai(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function wt(e){return{x:e.offsetLeft,y:e.offsetTop}}function Ci(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function Hi(e){return R(b(window,"load"),b(window,"resize")).pipe(Xe(0,je),f(()=>wt(e)),J(wt(e)))}function ln(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ut(e){return R(b(e,"scroll"),b(window,"scroll"),b(window,"resize")).pipe(Xe(0,je),f(()=>ln(e)),J(ln(e)))}var $i=new I,sl=j(()=>Y(new IntersectionObserver(e=>{for(let t of e)$i.next(t)},{threshold:0}))).pipe(g(e=>R(Ke,Y(e)).pipe(V(()=>e.disconnect()))),se(1));function Et(e){return sl.pipe($(t=>t.observe(e)),g(t=>$i.pipe(L(({target:r})=>r===e),V(()=>t.unobserve(e)),f(({isIntersecting:r})=>r))))}var cl=Object.create,la=Object.defineProperty,ll=Object.getOwnPropertyDescriptor,ul=Object.getOwnPropertyNames,pl=Object.getPrototypeOf,fl=Object.prototype.hasOwnProperty,ml=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),dl=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ul(t))!fl.call(e,o)&&o!==r&&la(e,o,{get:()=>t[o],enumerable:!(n=ll(t,o))||n.enumerable});return e},hl=(e,t,r)=>(r=e!=null?cl(pl(e)):{},dl(t||!e||!e.__esModule?la(r,"default",{value:e,enumerable:!0}):r,e)),vl=ml((e,t)=>{var r="Expected a function",n=NaN,o="[object Symbol]",i=/^\s+|\s+$/g,a=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,c=/^0o[0-7]+$/i,l=parseInt,u=typeof global=="object"&&global&&global.Object===Object&&global,p=typeof self=="object"&&self&&self.Object===Object&&self,d=u||p||Function("return this")(),m=Object.prototype,h=m.toString,v=Math.max,x=Math.min,w=function(){return d.Date.now()};function E(O,N,ee){var le,ce,Ne,bt,De,st,tt=0,Yt=!1,Mt=!1,vr=!0;if(typeof O!="function")throw new TypeError(r);N=M(N)||0,_(ee)&&(Yt=!!ee.leading,Mt="maxWait"in ee,Ne=Mt?v(M(ee.maxWait)||0,N):Ne,vr="trailing"in ee?!!ee.trailing:vr);function B(Te){var gt=le,br=ce;return le=ce=void 0,tt=Te,bt=O.apply(br,gt),bt}function C(Te){return tt=Te,De=setTimeout(W,N),Yt?B(Te):bt}function k(Te){var gt=Te-st,br=Te-tt,Ro=N-gt;return Mt?x(Ro,Ne-br):Ro}function D(Te){var gt=Te-st,br=Te-tt;return st===void 0||gt>=N||gt<0||Mt&&br>=Ne}function W(){var Te=w();if(D(Te))return Z(Te);De=setTimeout(W,k(Te))}function Z(Te){return De=void 0,vr&&le?B(Te):(le=ce=void 0,bt)}function We(){De!==void 0&&clearTimeout(De),tt=0,le=st=ce=De=void 0}function Gt(){return De===void 0?bt:Z(w())}function Nr(){var Te=w(),gt=D(Te);if(le=arguments,ce=this,st=Te,gt){if(De===void 0)return C(st);if(Mt)return De=setTimeout(W,N),B(st)}return De===void 0&&(De=setTimeout(W,N)),bt}return Nr.cancel=We,Nr.flush=Gt,Nr}function _(O){var N=typeof O;return!!O&&(N=="object"||N=="function")}function de(O){return!!O&&typeof O=="object"}function be(O){return typeof O=="symbol"||de(O)&&h.call(O)==o}function M(O){if(typeof O=="number")return O;if(be(O))return n;if(_(O)){var N=typeof O.valueOf=="function"?O.valueOf():O;O=_(N)?N+"":N}if(typeof O!="string")return O===0?O:+O;O=O.replace(i,"");var ee=s.test(O);return ee||c.test(O)?l(O.slice(2),ee?2:8):a.test(O)?n:+O}t.exports=E}),yn,K,ua,pa,Nt,Pi,fa,ma,da,lo,to,ro,bl,Ar={},ha=[],gl=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Pr=Array.isArray;function pt(e,t){for(var r in t)e[r]=t[r];return e}function uo(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function Wt(e,t,r){var n,o,i,a={};for(i in t)i=="key"?n=t[i]:i=="ref"?o=t[i]:a[i]=t[i];if(arguments.length>2&&(a.children=arguments.length>3?yn.call(arguments,2):r),typeof e=="function"&&e.defaultProps!=null)for(i in e.defaultProps)a[i]===void 0&&(a[i]=e.defaultProps[i]);return fn(e,a,n,o,null)}function fn(e,t,r,n,o){var i={type:e,props:t,key:r,ref:n,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o!=null?o:++ua,__i:-1,__u:0};return o==null&&K.vnode!=null&&K.vnode(i),i}function ft(e){return e.children}function at(e,t){this.props=e,this.context=t}function cr(e,t){if(t==null)return e.__?cr(e.__,e.__i+1):null;for(var r;ts&&Nt.sort(ma),e=Nt.shift(),s=Nt.length,e.__d&&(r=void 0,n=void 0,o=(n=(t=e).__v).__e,i=[],a=[],t.__P&&((r=pt({},n)).__v=n.__v+1,K.vnode&&K.vnode(r),po(t.__P,r,n,t.__n,t.__P.namespaceURI,32&n.__u?[o]:null,i,o!=null?o:cr(n),!!(32&n.__u),a),r.__v=n.__v,r.__.__k[r.__i]=r,_a(i,r,a),n.__e=n.__=null,r.__e!=o&&va(r)));vn.__r=0}function ba(e,t,r,n,o,i,a,s,c,l,u){var p,d,m,h,v,x,w,E=n&&n.__k||ha,_=t.length;for(c=_l(r,t,E,c,_),p=0;p<_;p++)(m=r.__k[p])!=null&&(d=m.__i==-1?Ar:E[m.__i]||Ar,m.__i=p,x=po(e,m,d,o,i,a,s,c,l,u),h=m.__e,m.ref&&d.ref!=m.ref&&(d.ref&&fo(d.ref,null,m),u.push(m.ref,m.__c||h,m)),v==null&&h!=null&&(v=h),(w=!!(4&m.__u))||d.__k===m.__k?c=ga(m,c,e,w):typeof m.type=="function"&&x!==void 0?c=x:h&&(c=h.nextSibling),m.__u&=-7);return r.__e=v,c}function _l(e,t,r,n,o){var i,a,s,c,l,u=r.length,p=u,d=0;for(e.__k=new Array(o),i=0;i0?fn(a.type,a.props,a.key,a.ref?a.ref:null,a.__v):a).__=e,a.__b=e.__b+1,s=null,(l=a.__i=yl(a,r,c,p))!=-1&&(p--,(s=r[l])&&(s.__u|=2)),s==null||s.__v==null?(l==-1&&(o>u?d--:oc?d--:d++,a.__u|=4))):e.__k[i]=null;if(p)for(i=0;i(u?1:0)){for(o=r-1,i=r+1;o>=0||i=0?o--:i++])!=null&&!(2&l.__u)&&s==l.key&&c==l.type)return a}return-1}function Ri(e,t,r){t[0]=="-"?e.setProperty(t,r!=null?r:""):e[t]=r==null?"":typeof r!="number"||gl.test(t)?r:r+"px"}function un(e,t,r,n,o){var i,a;e:if(t=="style")if(typeof r=="string")e.style.cssText=r;else{if(typeof n=="string"&&(e.style.cssText=n=""),n)for(t in n)r&&t in r||Ri(e.style,t,"");if(r)for(t in r)n&&r[t]==n[t]||Ri(e.style,t,r[t])}else if(t[0]=="o"&&t[1]=="n")i=t!=(t=t.replace(da,"$1")),a=t.toLowerCase(),t=a in e||t=="onFocusOut"||t=="onFocusIn"?a.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+i]=r,r?n?r.u=n.u:(r.u=lo,e.addEventListener(t,i?ro:to,i)):e.removeEventListener(t,i?ro:to,i);else{if(o=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=r!=null?r:"";break e}catch(s){}typeof r=="function"||(r==null||r===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&r==1?"":r))}}function ji(e){return function(t){if(this.l){var r=this.l[t.type+e];if(t.t==null)t.t=lo++;else if(t.t0?e:Pr(e)?e.map(ya):pt({},e)}function xl(e,t,r,n,o,i,a,s,c){var l,u,p,d,m,h,v,x=r.props,w=t.props,E=t.type;if(E=="svg"?o="http://www.w3.org/2000/svg":E=="math"?o="http://www.w3.org/1998/Math/MathML":o||(o="http://www.w3.org/1999/xhtml"),i!=null){for(l=0;l=r.__.length&&r.__.push({}),r.__[e]}function bn(e){return $r=1,Tl(Ta,e)}function Tl(e,t,r){var n=mo(Hr++,2);if(n.t=e,!n.__c&&(n.__=[r?r(t):Ta(void 0,t),function(s){var c=n.__N?n.__N[0]:n.__[0],l=n.t(c,s);c!==l&&(n.__N=[l,n.__[1]],n.__c.setState({}))}],n.__c=ve,!ve.__f)){var o=function(s,c,l){if(!n.__c.__H)return!0;var u=n.__c.__H.__.filter(function(d){return!!d.__c});if(u.every(function(d){return!d.__N}))return!i||i.call(this,s,c,l);var p=n.__c.props!==s;return u.forEach(function(d){if(d.__N){var m=d.__[0];d.__=d.__N,d.__N=void 0,m!==d.__[0]&&(p=!0)}}),i&&i.call(this,s,c,l)||p};ve.__f=!0;var i=ve.shouldComponentUpdate,a=ve.componentWillUpdate;ve.componentWillUpdate=function(s,c,l){if(this.__e){var u=i;i=void 0,o(s,c,l),i=u}a&&a.call(this,s,c,l)},ve.shouldComponentUpdate=o}return n.__N||n.__}function mt(e,t){var r=mo(Hr++,3);!we.__s&&Ea(r.__H,t)&&(r.__=e,r.u=t,ve.__H.__h.push(r))}function Vt(e){return $r=5,ur(function(){return{current:e}},[])}function ur(e,t){var r=mo(Hr++,7);return Ea(r.__H,t)&&(r.__=e(),r.__H=t,r.__h=e),r.__}function Sl(e,t){return $r=8,ur(function(){return e},t)}function Ol(){for(var e;e=wa.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(mn),e.__H.__h.forEach(oo),e.__H.__h=[]}catch(t){e.__H.__h=[],we.__e(t,e.__v)}}we.__b=function(e){ve=null,Ui&&Ui(e)},we.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),zi&&zi(e,t)},we.__r=function(e){Ni&&Ni(e),Hr=0;var t=(ve=e.__c).__H;t&&(Zn===ve?(t.__h=[],ve.__h=[],t.__.forEach(function(r){r.__N&&(r.__=r.__N),r.u=r.__N=void 0})):(t.__h.forEach(mn),t.__h.forEach(oo),t.__h=[],Hr=0)),Zn=ve},we.diffed=function(e){Di&&Di(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(wa.push(t)!==1&&Fi===we.requestAnimationFrame||((Fi=we.requestAnimationFrame)||Ll)(Ol)),t.__H.__.forEach(function(r){r.u&&(r.__H=r.u),r.u=void 0})),Zn=ve=null},we.__c=function(e,t){t.some(function(r){try{r.__h.forEach(mn),r.__h=r.__h.filter(function(n){return!n.__||oo(n)})}catch(n){t.some(function(o){o.__h&&(o.__h=[])}),t=[],we.__e(n,r.__v)}}),Wi&&Wi(e,t)},we.unmount=function(e){Vi&&Vi(e);var t,r=e.__c;r&&r.__H&&(r.__H.__.forEach(function(n){try{mn(n)}catch(o){t=o}}),r.__H=void 0,t&&we.__e(t,r.__v))};var qi=typeof requestAnimationFrame=="function";function Ll(e){var t,r=function(){clearTimeout(n),qi&&cancelAnimationFrame(t),setTimeout(e)},n=setTimeout(r,35);qi&&(t=requestAnimationFrame(r))}function mn(e){var t=ve,r=e.__c;typeof r=="function"&&(e.__c=void 0,r()),ve=t}function oo(e){var t=ve;e.__c=e.__(),ve=t}function Ea(e,t){return!e||e.length!==t.length||t.some(function(r,n){return r!==e[n]})}function Ta(e,t){return typeof t=="function"?t(e):t}function Ml(e,t){for(var r in t)e[r]=t[r];return e}function Ki(e,t){for(var r in e)if(r!=="__source"&&!(r in t))return!0;for(var n in t)if(n!=="__source"&&e[n]!==t[n])return!0;return!1}function Bi(e,t){this.props=e,this.context=t}(Bi.prototype=new at).isPureReactComponent=!0,Bi.prototype.shouldComponentUpdate=function(e,t){return Ki(this.props,e)||Ki(this.state,t)};var Yi=K.__b;K.__b=function(e){e.type&&e.type.__f&&e.ref&&(e.props.ref=e.ref,e.ref=null),Yi&&Yi(e)};var Yx=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.forward_ref")||3911,kl=K.__e;K.__e=function(e,t,r,n){if(e.then){for(var o,i=t;i=i.__;)if((o=i.__c)&&o.__c)return t.__e==null&&(t.__e=r.__e,t.__k=r.__k),o.__c(e,t)}kl(e,t,r,n)};var Gi=K.unmount;function Sa(e,t,r){return e&&(e.__c&&e.__c.__H&&(e.__c.__H.__.forEach(function(n){typeof n.__c=="function"&&n.__c()}),e.__c.__H=null),(e=Ml({},e)).__c!=null&&(e.__c.__P===r&&(e.__c.__P=t),e.__c.__e=!0,e.__c=null),e.__k=e.__k&&e.__k.map(function(n){return Sa(n,t,r)})),e}function Oa(e,t,r){return e&&r&&(e.__v=null,e.__k=e.__k&&e.__k.map(function(n){return Oa(n,t,r)}),e.__c&&e.__c.__P===t&&(e.__e&&r.appendChild(e.__e),e.__c.__e=!0,e.__c.__P=r)),e}function Qn(){this.__u=0,this.o=null,this.__b=null}function La(e){var t=e.__.__c;return t&&t.__a&&t.__a(e)}function pn(){this.i=null,this.l=null}K.unmount=function(e){var t=e.__c;t&&t.__R&&t.__R(),t&&32&e.__u&&(e.type=null),Gi&&Gi(e)},(Qn.prototype=new at).__c=function(e,t){var r=t.__c,n=this;n.o==null&&(n.o=[]),n.o.push(r);var o=La(n.__v),i=!1,a=function(){i||(i=!0,r.__R=null,o?o(s):s())};r.__R=a;var s=function(){if(!--n.__u){if(n.state.__a){var c=n.state.__a;n.__v.__k[0]=Oa(c,c.__c.__P,c.__c.__O)}var l;for(n.setState({__a:n.__b=null});l=n.o.pop();)l.forceUpdate()}};n.__u++||32&t.__u||n.setState({__a:n.__b=n.__v.__k[0]}),e.then(a,a)},Qn.prototype.componentWillUnmount=function(){this.o=[]},Qn.prototype.render=function(e,t){if(this.__b){if(this.__v.__k){var r=document.createElement("div"),n=this.__v.__k[0].__c;this.__v.__k[0]=Sa(this.__b,r,n.__O=n.__P)}this.__b=null}var o=t.__a&&Wt(ft,null,e.fallback);return o&&(o.__u&=-33),[Wt(ft,null,t.__a?null:e.children),o]};var Ji=function(e,t,r){if(++r[1]===r[0]&&e.l.delete(t),e.props.revealOrder&&(e.props.revealOrder[0]!=="t"||!e.l.size))for(r=e.i;r;){for(;r.length>3;)r.pop()();if(r[1]Object.freeze({get current(){return t.current}}),[])}var Nl=typeof globalThis<"u"&&typeof navigator<"u"&&typeof document<"u";function Dl(e,...t){var r;(r=e==null?void 0:e.addEventListener)==null||r.call(e,...t)}function Wl(e,...t){var r;(r=e==null?void 0:e.removeEventListener)==null||r.call(e,...t)}var Vl=(e,t)=>Object.hasOwn(e,t),zl=()=>!0,ql=()=>!1;function Kl(e=!1){let t=Vt(e),r=Sl(()=>t.current,[]);return mt(()=>(t.current=!0,()=>{t.current=!1}),[]),r}function Bl(e,...t){let r=Kl(),n=ka(t[1]),o=ur(()=>function(...i){r()&&(typeof n.current=="function"?n.current.apply(this,i):typeof n.current.handleEvent=="function"&&n.current.handleEvent.apply(this,i))},[]);mt(()=>{let i=Yl(e)?e.current:e;if(!i)return;let a=t.slice(2);return Dl(i,t[0],o,...a),()=>{Wl(i,t[0],o,...a)}},[e,t[0]])}function Yl(e){return e!==null&&typeof e=="object"&&Vl(e,"current")}var Gl=e=>typeof e=="function"?e:typeof e=="string"?t=>t.key===e:e?zl:ql,Jl=Nl?globalThis:null;function Aa(e,t,r=[],n={}){let{event:o="keydown",target:i=Jl,eventOptions:a}=n,s=ka(t),c=ur(()=>{let l=Gl(e);return function(u){l(u)&&s.current.call(this,u)}},r);Bl(i,o,c,a)}function Ca(e){var t,r,n="";if(typeof e=="string"||typeof e=="number")n+=e;else if(typeof e=="object")if(Array.isArray(e)){var o=e.length;for(t=0;t1)St--;else{for(var e,t=!1;kr!==void 0;){var r=kr;for(kr=void 0,io++;r!==void 0;){var n=r.o;if(r.o=void 0,r.f&=-3,!(8&r.f)&&Pa(r))try{r.c()}catch(o){t||(e=o,t=!0)}r=n}}if(io=0,St--,t)throw e}}function Ql(e){if(St>0)return e();St++;try{return e()}finally{xn()}}var ae=void 0;function Ha(e){var t=ae;ae=void 0;try{return e()}finally{ae=t}}var kr=void 0,St=0,io=0,gn=0;function $a(e){if(ae!==void 0){var t=e.n;if(t===void 0||t.t!==ae)return t={i:0,S:e,p:ae.s,n:void 0,t:ae,e:void 0,x:void 0,r:t},ae.s!==void 0&&(ae.s.n=t),ae.s=t,e.n=t,32&ae.f&&e.S(t),t;if(t.i===-1)return t.i=0,t.n!==void 0&&(t.n.p=t.p,t.p!==void 0&&(t.p.n=t.n),t.p=ae.s,t.n=void 0,ae.s.n=t,ae.s=t),t}}function Ce(e,t){this.v=e,this.i=0,this.n=void 0,this.t=void 0,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}Ce.prototype.brand=Zl;Ce.prototype.h=function(){return!0};Ce.prototype.S=function(e){var t=this,r=this.t;r!==e&&e.e===void 0&&(e.x=r,this.t=e,r!==void 0?r.e=e:Ha(function(){var n;(n=t.W)==null||n.call(t)}))};Ce.prototype.U=function(e){var t=this;if(this.t!==void 0){var r=e.e,n=e.x;r!==void 0&&(r.x=n,e.e=void 0),n!==void 0&&(n.e=r,e.x=void 0),e===this.t&&(this.t=n,n===void 0&&Ha(function(){var o;(o=t.Z)==null||o.call(t)}))}};Ce.prototype.subscribe=function(e){var t=this;return qt(function(){var r=t.value,n=ae;ae=void 0;try{e(r)}finally{ae=n}},{name:"sub"})};Ce.prototype.valueOf=function(){return this.value};Ce.prototype.toString=function(){return this.value+""};Ce.prototype.toJSON=function(){return this.value};Ce.prototype.peek=function(){var e=ae;ae=void 0;try{return this.value}finally{ae=e}};Object.defineProperty(Ce.prototype,"value",{get:function(){var e=$a(this);return e!==void 0&&(e.i=this.i),this.v},set:function(e){if(e!==this.v){if(io>100)throw new Error("Cycle detected");this.v=e,this.i++,gn++,St++;try{for(var t=this.t;t!==void 0;t=t.x)t.t.N()}finally{xn()}}}});function Ot(e,t){return new Ce(e,t)}function Pa(e){for(var t=e.s;t!==void 0;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return!0;return!1}function Ia(e){for(var t=e.s;t!==void 0;t=t.n){var r=t.S.n;if(r!==void 0&&(t.r=r),t.S.n=t,t.i=-1,t.n===void 0){e.s=t;break}}}function Ra(e){for(var t=e.s,r=void 0;t!==void 0;){var n=t.p;t.i===-1?(t.S.U(t),n!==void 0&&(n.n=t.n),t.n!==void 0&&(t.n.p=n)):r=t,t.S.n=t.r,t.r!==void 0&&(t.r=void 0),t=n}e.s=r}function Kt(e,t){Ce.call(this,void 0),this.x=e,this.s=void 0,this.g=gn-1,this.f=4,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}Kt.prototype=new Ce;Kt.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if((36&this.f)==32||(this.f&=-5,this.g===gn))return!0;if(this.g=gn,this.f|=1,this.i>0&&!Pa(this))return this.f&=-2,!0;var e=ae;try{Ia(this),ae=this;var t=this.x();(16&this.f||this.v!==t||this.i===0)&&(this.v=t,this.f&=-17,this.i++)}catch(r){this.v=r,this.f|=16,this.i++}return ae=e,Ra(this),this.f&=-2,!0};Kt.prototype.S=function(e){if(this.t===void 0){this.f|=36;for(var t=this.s;t!==void 0;t=t.n)t.S.S(t)}Ce.prototype.S.call(this,e)};Kt.prototype.U=function(e){if(this.t!==void 0&&(Ce.prototype.U.call(this,e),this.t===void 0)){this.f&=-33;for(var t=this.s;t!==void 0;t=t.n)t.S.U(t)}};Kt.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var e=this.t;e!==void 0;e=e.x)e.t.N()}};Object.defineProperty(Kt.prototype,"value",{get:function(){if(1&this.f)throw new Error("Cycle detected");var e=$a(this);if(this.h(),e!==void 0&&(e.i=this.i),16&this.f)throw this.v;return this.v}});function ta(e,t){return new Kt(e,t)}function ja(e){var t=e.u;if(e.u=void 0,typeof t=="function"){St++;var r=ae;ae=void 0;try{t()}catch(n){throw e.f&=-2,e.f|=8,ho(e),n}finally{ae=r,xn()}}}function ho(e){for(var t=e.s;t!==void 0;t=t.n)t.S.U(t);e.x=void 0,e.s=void 0,ja(e)}function eu(e){if(ae!==this)throw new Error("Out-of-order effect");Ra(this),ae=e,this.f&=-2,8&this.f&&ho(this),xn()}function pr(e,t){this.x=e,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32,this.name=t==null?void 0:t.name}pr.prototype.c=function(){var e=this.S();try{if(8&this.f||this.x===void 0)return;var t=this.x();typeof t=="function"&&(this.u=t)}finally{e()}};pr.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1,this.f&=-9,ja(this),Ia(this),St++;var e=ae;return ae=this,eu.bind(this,e)};pr.prototype.N=function(){2&this.f||(this.f|=2,this.o=kr,kr=this)};pr.prototype.d=function(){this.f|=8,1&this.f||ho(this)};pr.prototype.dispose=function(){this.d()};function qt(e,t){var r=new pr(e,t);try{r.c()}catch(o){throw r.d(),o}var n=r.d.bind(r);return n[Symbol.dispose]=n,n}var Fa,vo,eo,Ua=[];qt(function(){Fa=this.N})();function fr(e,t){K[e]=t.bind(null,K[e]||function(){})}function _n(e){eo&&eo(),eo=e&&e.S()}function Na(e){var t=this,r=e.data,n=ru(r);n.value=r;var o=ur(function(){for(var s=t,c=t.__v;c=c.__;)if(c.__c){c.__c.__$f|=4;break}var l=ta(function(){var m=n.value.value;return m===0?0:m===!0?"":m||""}),u=ta(function(){return!Array.isArray(l.value)&&!pa(l.value)}),p=qt(function(){if(this.N=Da,u.value){var m=l.value;s.__v&&s.__v.__e&&s.__v.__e.nodeType===3&&(s.__v.__e.data=m)}}),d=t.__$u.d;return t.__$u.d=function(){p(),d.call(this)},[u,l]},[]),i=o[0],a=o[1];return i.value?a.peek():a.value}Na.displayName="ReactiveTextNode";Object.defineProperties(Ce.prototype,{constructor:{configurable:!0,value:void 0},type:{configurable:!0,value:Na},props:{configurable:!0,get:function(){return{data:this}}},__b:{configurable:!0,value:1}});fr("__b",function(e,t){if(typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),typeof t.type=="string"){var r,n=t.props;for(var o in n)if(o!=="children"){var i=n[o];i instanceof Ce&&(r||(t.__np=r={}),r[o]=i,n[o]=i.peek())}}e(t)});fr("__r",function(e,t){if(typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.enterComponent(t),t.type!==ft){_n();var r,n=t.__c;n&&(n.__$f&=-2,(r=n.__$u)===void 0&&(n.__$u=r=(function(o){var i;return qt(function(){i=this}),i.c=function(){n.__$f|=1,n.setState({})},i})())),vo=n,_n(r)}e(t)});fr("__e",function(e,t,r,n){typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),_n(),vo=void 0,e(t,r,n)});fr("diffed",function(e,t){typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),_n(),vo=void 0;var r;if(typeof t.type=="string"&&(r=t.__e)){var n=t.__np,o=t.props;if(n){var i=r.U;if(i)for(var a in i){var s=i[a];s!==void 0&&!(a in n)&&(s.d(),i[a]=void 0)}else i={},r.U=i;for(var c in n){var l=i[c],u=n[c];l===void 0?(l=tu(r,c,u,o),i[c]=l):l.o(u,o)}}}e(t)});function tu(e,t,r,n){var o=t in e&&e.ownerSVGElement===void 0,i=Ot(r);return{o:function(a,s){i.value=a,n=s},d:qt(function(){this.N=Da;var a=i.value.value;n[t]!==a&&(n[t]=a,o?e[t]=a:a?e.setAttribute(t,a):e.removeAttribute(t))})}}fr("unmount",function(e,t){if(typeof t.type=="string"){var r=t.__e;if(r){var n=r.U;if(n){r.U=void 0;for(var o in n){var i=n[o];i&&i.d()}}}}else{var a=t.__c;if(a){var s=a.__$u;s&&(a.__$u=void 0,s.d())}}e(t)});fr("__h",function(e,t,r,n){(n<3||n===9)&&(t.__$f|=2),e(t,r,n)});at.prototype.shouldComponentUpdate=function(e,t){var r=this.__$u,n=r&&r.s!==void 0;for(var o in t)return!0;if(this.__f||typeof this.u=="boolean"&&this.u===!0){var i=2&this.__$f;if(!(n||i||4&this.__$f)||1&this.__$f)return!0}else if(!(n||4&this.__$f)||3&this.__$f)return!0;for(var a in e)if(a!=="__source"&&e[a]!==this.props[a])return!0;for(var s in this.props)if(!(s in e))return!0;return!1};function ru(e,t){return bn(function(){return Ot(e,t)})[0]}var nu=function(e){queueMicrotask(function(){queueMicrotask(e)})};function ou(){Ql(function(){for(var e;e=Ua.shift();)Fa.call(e)})}function Da(){Ua.push(this)===1&&(K.requestAnimationFrame||nu)(ou)}var ao=[0];for(let e=0;e<32;e++)ao.push(ao[e]|1<>>5]>>>e&1}set(e){this.data[e>>>5]|=1<<(e&31)}forEach(e){let t=this.size&31;for(let r=0;r{var r;return(r=t.tags)==null?void 0:r.length})&&(matchMedia("(max-width: 768px)").matches||Wa())}function Dt(){Qe.value=He(H({},Qe.value),{hideSearch:!Qe.value.hideSearch})}function Wa(){Qe.value=He(H({},Qe.value),{hideFilters:!Qe.value.hideFilters})}function dn(){return Qe.value.selectedItem}function so(e){Qe.value=He(H({},Qe.value),{selectedItem:e})}function su(){var e,t;return(t=(e=lr.value)==null?void 0:e.items)!=null?t:[]}function wn(){return typeof Se.value.input=="string"?Se.value.input:""}function Va(e){let t=za();e.length&&!t.length?Se.value=He(H({},Se.value),{page:void 0,input:e}):!e.length&&t.length?Se.value=He(H({},Se.value),{page:void 0,input:{type:"operator",data:{operator:"not",operands:[]}}}):Se.value=He(H({},Se.value),{page:void 0,input:e})}function cu(){typeof it.value.pagination.next<"u"&&(Se.value=He(H({},Se.value),{page:it.value.pagination.next}))}function lu(e){let t=Se.value.filter.input;if("type"in t&&t.type==="operator"){for(let r of t.data.operands)if("type"in r&&r.type==="value"&&typeof r.data.value=="string"&&r.data.value===e)return!0}return!1}function za(){let e=Se.value.filter.input,t=[];if("type"in e&&e.type==="operator")for(let r of e.data.operands)"type"in r&&r.type==="value"&&typeof r.data.value=="string"&&t.push(r.data.value);return t}function uu(e){let t=Se.value.filter.input,r=[];if("type"in t&&t.type==="operator")for(let n of t.data.operands)"type"in n&&n.type==="value"&&typeof n.data.value=="string"&&r.push(n.data.value);if(r.includes(e)){let n=r.indexOf(e);n>-1&&r.splice(n,1)}else r.push(e);Se.value=He(H({},Se.value),{page:void 0,filter:He(H({},Se.value.filter),{input:{type:"operator",data:{operator:"and",operands:r.map(n=>({type:"value",data:{field:"tags",value:n}}))}}})}),Va(wn())}function pu(){return it.value.items}function fu(){return it.value.total}function mu(){var e;for(let t of(e=it.value.aggregations)!=null?e:[])if(t.type==="term")return t.data.value;return[]}function sr(){return Qe.value.hideSearch}function du(){return Qe.value.hideFilters}function qa(){var e;return(e=Ka.value.highlight)!=null?e:!1}var Qe=Ot({hideSearch:!0,hideFilters:!0,selectedItem:0}),Ka=Ot({}),lr=Ot(),na=Ot(),Se=Ot({input:"",filter:{input:{type:"operator",data:{operator:"and",operands:[]}},aggregation:{input:[{type:"term",data:{field:"tags"}}]}}}),it=Ot({items:[],query:{select:{documents:new ra(0),terms:new ra(0)},values:[]},pagination:{total:0}});function hu(e,t,r){for(let n=0;tr&&t(0,o,r,r=i);continue;case 62:e.charCodeAt(r+1)===47?t(2,--o,r,r=i+1):hu(e,r,n)?t(3,o,r,r=i+1):t(1,o++,r,r=i+1)}i>r&&t(0,o,r,i)}function bu(e,t=0,r=e.length){let n=++t;e:for(let l=0;n{let i=[],a=[],{onElement:s,onText:c=gu}=typeof r=="function"?{onElement:r}:r,l=0,u=0;return e(t,(p,d,m,h)=>{if(p===0)i[l++]=c(t,m,h),a[u++]={value:null,depth:d};else if(p&1&&(a[u++]={value:bu(t,m,h),depth:d}),p&2)for(let v=0;u>=0;v++){let{value:x,depth:w}=a[--u];if(w>d)continue;let E=i.slice(l-=v,l+v);i[l++]=s(x,E),u++;break}},n,o),i.slice(0,l)}}function yu(e){return e.replace(/[&<>]/g,t=>{switch(t.charCodeAt(0)){case 38:return"&";case 60:return"<";case 62:return">"}})}function hn(e){return e.replace(/&(amp|[lg]t);/g,t=>{switch(t.charCodeAt(1)){case 97:return"&";case 108:return"<";case 103:return">"}})}function xu(e,t){return{start:e.start+t,end:e.end+t,value:e.value}}function wu(e,t,r){return e.slice(t,r)}function Eu(e){let{onHighlight:t,onText:r=wu}=typeof e=="function"?{onHighlight:e}:e;return(n,o,i=0,a=n.length)=>{var l;let s=[],c=(l=o==null?void 0:o.ranges)!=null?l:[];for(let u=0,p=i;ua)break;let m=c[u].end;if(mi&&s.push(r(n,i,d));let{value:h}=c[u];s.push(t(n,{start:d,end:i=m,value:h}))}return i{let o=n.data;switch(o.type){case 1:na.value=!0;break;case 3:typeof o.data.pagination.prev<"u"?it.value=He(H({},it.value),{pagination:o.data.pagination,items:[...it.value.items,...o.data.items]}):(it.value=o.data,so(0));break}},qt(()=>{lr.value&&r.postMessage({type:0,data:lr.value})}),qt(()=>{na.value&&r.postMessage({type:2,data:Se.value})})}var oa={container:"p",hidden:"m"};function ku(e){return z("div",{class:zt(oa.container,{[oa.hidden]:e.hidden}),onClick:()=>Dt()})}var ia={container:"r",disabled:"c"};function co(e){return z("button",{class:zt(ia.container,{[ia.disabled]:!e.onClick}),onClick:e.onClick,children:e.children})}var aa=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),Au=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,r,n)=>n?n.toUpperCase():r.toLowerCase()),sa=e=>{let t=Au(e);return t.charAt(0).toUpperCase()+t.slice(1)},Cu=(...e)=>e.filter((t,r,n)=>!!t&&t.trim()!==""&&n.indexOf(t)===r).join(" ").trim(),Hu={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},$u=c=>{var l=c,{color:e="currentColor",size:t=24,strokeWidth:r=2,absoluteStrokeWidth:n,children:o,iconNode:i,class:a=""}=l,s=gr(l,["color","size","strokeWidth","absoluteStrokeWidth","children","iconNode","class"]);return Wt("svg",H(He(H({},Hu),{width:String(t),height:t,stroke:e,"stroke-width":n?Number(r)*24/Number(t):r,class:["lucide",a].join(" ")}),s),[...i.map(([u,p])=>Wt(u,p)),...Cr(o)])},bo=(e,t)=>{let r=a=>{var s=a,{class:n="",children:o}=s,i=gr(s,["class","children"]);return Wt($u,He(H({},i),{iconNode:t,class:Cu(`lucide-${aa(sa(e))}`,`lucide-${aa(e)}`,n)}),o)};return r.displayName=sa(e),r},Pu=bo("corner-down-left",[["path",{d:"M20 4v7a4 4 0 0 1-4 4H4",key:"6o5b7l"}],["path",{d:"m9 10-5 5 5 5",key:"1kshq7"}]]),Iu=bo("list-filter",[["path",{d:"M2 5h20",key:"1fs1ex"}],["path",{d:"M6 12h12",key:"8npq4p"}],["path",{d:"M9 19h6",key:"456am0"}]]),Ru=bo("search",[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]]),Gx=hl(vl(),1);function ju({threshold:e=0,root:t=null,rootMargin:r="0%",freezeOnceVisible:n=!1,initialIsIntersecting:o=!1,onChange:i}={}){var a;let[s,c]=bn(null),[l,u]=bn(()=>({isIntersecting:o,entry:void 0})),p=Vt();p.current=i;let d=((a=l.entry)==null?void 0:a.isIntersecting)&&n;mt(()=>{if(!s||!("IntersectionObserver"in window)||d)return;let v,x=new IntersectionObserver(w=>{let E=Array.isArray(x.thresholds)?x.thresholds:[x.thresholds];w.forEach(_=>{let de=_.isIntersecting&&E.some(be=>_.intersectionRatio>=be);u({isIntersecting:de,entry:_}),p.current&&p.current(de,_),de&&n&&v&&(v(),v=void 0)})},{threshold:e,root:t,rootMargin:r});return x.observe(s),()=>{x.disconnect()}},[s,JSON.stringify(e),t,r,d,n]);let m=Vt(null);mt(()=>{var v;!s&&(v=l.entry)!=null&&v.target&&!n&&!d&&m.current!==l.entry.target&&(m.current=l.entry.target,u({isIntersecting:o,entry:void 0}))},[s,l.entry,n,d,o]);let h=[c,!!l.isIntersecting,l.entry];return h.ref=h[0],h.isIntersecting=h[1],h.entry=h[2],h}var lt={container:"n",hidden:"l",content:"u",pop:"d",badge:"y",sidebar:"i",controls:"w",results:"k",loadmore:"z"};function Fu(e){let{isIntersecting:t,ref:r}=ju({threshold:0});mt(()=>{t&&cu()},[t]);let n=Vt(null);mt(()=>{n.current&&typeof Se.value.page>"u"&&n.current.scrollTo({top:0,behavior:"smooth"})},[Se.value]);let o=za();return z("div",{class:zt(lt.container,{[lt.hidden]:e.hidden}),children:[z("div",{class:lt.content,children:[z("div",{class:lt.controls,children:[z(co,{onClick:Dt,children:z(Ru,{})}),z(Nu,{focus:!e.hidden}),z(co,{onClick:Wa,children:[z(Iu,{}),o.length>0&&z("span",{class:lt.badge,children:o.length})]})]}),z("div",{class:lt.results,ref:n,children:[z(Du,{keyboard:!e.hidden}),z("div",{class:lt.loadmore,ref:r})]})]}),z("div",{class:zt(lt.sidebar,{[lt.hidden]:du()}),children:z(Uu,{})})]})}var Tt={container:"X",list:"j",heading:"F",title:"I",item:"o",active:"g",value:"R",count:"q"};function Uu(e){let t=mu();return t.sort((r,n)=>n.node.count-r.node.count),z("div",{class:Tt.container,children:[z("h3",{class:Tt.heading,children:"Filters"}),z("h4",{class:Tt.title,children:"Tags"}),z("ol",{class:Tt.list,children:t.map(r=>z("li",{class:zt(Tt.item,{[Tt.active]:lu(r.node.value)}),onClick:()=>uu(r.node.value),children:[z("span",{class:Tt.value,children:r.node.value}),z("span",{class:Tt.count,children:r.node.count})]}))})]})}var ca={container:"f"};function Nu(e){let t=Vt(null);return mt(()=>{var r,n;e.focus?(r=t.current)==null||r.focus():(n=t.current)==null||n.blur()},[e.focus]),z("div",{class:ca.container,children:z("input",{ref:t,type:"text",class:ca.content,value:hn(wn()),onInput:r=>Va(yu(r.currentTarget.value)),autocapitalize:"off",autocomplete:"off",autocorrect:"off",placeholder:"Search",spellcheck:!1,role:"combobox"})})}var ut={container:"b",heading:"A",item:"a",active:"h",wrapper:"B",actions:"s",title:"x",path:"t"};function Ga(){let[e,t]=bn(!1);return mt(()=>{let r=()=>t(!0),n=()=>t(!1);return document.addEventListener("compositionstart",r),document.addEventListener("compositionend",n),()=>{document.removeEventListener("compositionstart",r),document.removeEventListener("compositionend",n)}},[]),e}function Du(e){var s;let t=su(),r=pu(),n=dn(),o=Vt([]),i=Ga();mt(()=>{let c=o.current[n];c&&c.scrollIntoView({block:"center",behavior:"smooth"})},[n]),Aa(e.keyboard,c=>{if(i)return;let l=dn();c.key==="ArrowDown"?(c.preventDefault(),so(Math.min(l+1,r.length-1))):c.key==="ArrowUp"&&(c.preventDefault(),so(Math.max(l-1,0)))},[e.keyboard,i]);let a=(s=fu())!=null?s:0;return z(ft,{children:[r.length>0&&z("h3",{class:ut.heading,children:[z("span",{class:ut.bubble,children:new Intl.NumberFormat("en-US").format(a)})," ","results"]}),z("ol",{class:ut.container,children:r.map((c,l)=>{var m;let u=Ba(t[c.id].title,c.matches.find(({field:h})=>h==="title")),p=Mu((m=t[c.id].path)!=null?m:[],c.matches.find(({field:h})=>h==="path")),d=t[c.id].location;if(qa()){let h=encodeURIComponent(wn()),[v,x]=d.split("#",2);d=`${v}?h=${h.replace(/%20/g,"+")}`,typeof x<"u"&&(d+=`#${x}`)}return z("li",{children:z("a",{ref:h=>{o.current[l]=h},href:d,onClick:()=>Dt(),class:zt(ut.item,{[ut.active]:l===dn()}),children:[z("div",{class:ut.wrapper,children:[z("h2",{class:ut.title,children:u}),z("menu",{class:ut.path,children:p.map(h=>z("li",{children:h}))})]}),z("nav",{class:ut.actions,children:z(co,{children:z(Pu,{})})})]})})})})]})}var Wu={container:"e"};function Vu(e){let t=Ga();return Aa(!0,r=>{var n,o,i,a,s;if(!t)if((r.metaKey||r.ctrlKey)&&r.key==="k")r.preventDefault(),Dt();else if((r.metaKey||r.ctrlKey)&&r.key==="j")document.body.classList.toggle("dark");else if(r.key==="Enter"&&!sr()){r.preventDefault();let c=dn(),l=(o=(n=it.value)==null?void 0:n.items[c])==null?void 0:o.id;if((a=(i=lr.value)==null?void 0:i.items[l])!=null&&a.location){Dt();let u=(s=lr.value)==null?void 0:s.items[l].location;if(qa()){let p=encodeURIComponent(wn()),[d,m]=u.split("#",2);u=`${d}?h=${p.replace(/%20/g,"+")}`,typeof m<"u"&&(u+=`#${m}`)}window.location.href=u}}else r.key==="Escape"&&!sr()&&(r.preventDefault(),Dt())},[t]),z("div",{class:Wu.container,children:[z(ku,{hidden:sr()}),z(Fu,{hidden:sr()})]})}function Ja(e,t){au(e),El(z(Vu,{}),t)}function go(){Dt()}function zu(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function qu(){return R(b(window,"compositionstart").pipe(f(()=>!0)),b(window,"compositionend").pipe(f(()=>!1))).pipe(J(!1))}function Xa(){let e=b(window,"keydown").pipe(f(t=>({mode:sr()?"global":"search",type:t.key,meta:t.ctrlKey||t.metaKey,claim(){t.preventDefault(),t.stopPropagation()}})),L(({mode:t,type:r})=>{if(t==="global"){let n=xt();if(typeof n!="undefined")return!zu(n,r)}return!0}),xe());return qu().pipe(g(t=>t?y:e))}function Ye(){return new URL(location.href)}function dt(e,t=!1){if(X("navigation.instant")&&!t){let r=A("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function Za(){return new I}function Qa(){return location.hash.slice(1)}function es(e){let t=A("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function _o(e){return R(b(window,"hashchange"),e).pipe(f(Qa),J(Qa()),L(t=>t.length>0),se(1))}function ts(e){return _o(e).pipe(f(t=>Le(`[id="${t}"]`)),L(t=>typeof t!="undefined"))}function Ir(e){let t=matchMedia(e);return an(r=>t.addListener(()=>r(t.matches))).pipe(J(t.matches))}function rs(){let e=matchMedia("print");return R(b(window,"beforeprint").pipe(f(()=>!0)),b(window,"afterprint").pipe(f(()=>!1))).pipe(J(e.matches))}function yo(e,t){return e.pipe(g(r=>r?t():y))}function xo(e,t){return new U(r=>{let n=new XMLHttpRequest;return n.open("GET",`${e}`),n.responseType="blob",n.addEventListener("load",()=>{n.status>=200&&n.status<300?(r.next(n.response),r.complete()):r.error(new Error(n.statusText))}),n.addEventListener("error",()=>{r.error(new Error("Network error"))}),n.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(n.addEventListener("progress",o=>{var i;if(o.lengthComputable)t.progress$.next(o.loaded/o.total*100);else{let a=(i=n.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(o.loaded/+a*100)}}),t.progress$.next(5)),n.send(),()=>n.abort()})}function et(e,t){return xo(e,t).pipe(g(r=>r.text()),f(r=>JSON.parse(r)),se(1))}function En(e,t){let r=new DOMParser;return xo(e,t).pipe(g(n=>n.text()),f(n=>r.parseFromString(n,"text/html")),se(1))}function ns(e,t){let r=new DOMParser;return xo(e,t).pipe(g(n=>n.text()),f(n=>r.parseFromString(n,"text/xml")),se(1))}var wo={drawer:G("[data-md-toggle=drawer]"),search:G("[data-md-toggle=search]")};function Eo(e,t){wo[e].checked!==t&&wo[e].click()}function Tn(e){let t=wo[e];return b(t,"change").pipe(f(()=>t.checked),J(t.checked))}function os(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function is(){return R(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(f(os),J(os()))}function as(){return{width:innerWidth,height:innerHeight}}function ss(){return b(window,"resize",{passive:!0}).pipe(f(as),J(as()))}function cs(){return re([is(),ss()]).pipe(f(([e,t])=>({offset:e,size:t})),se(1))}function Sn(e,{viewport$:t,header$:r}){let n=t.pipe(fe("size")),o=re([n,r]).pipe(f(()=>wt(e)));return re([r,t,o]).pipe(f(([{height:i},{offset:a,size:s},{x:c,y:l}])=>({offset:{x:a.x-c,y:a.y-l+i},size:s})))}var Ku=G("#__config"),mr=JSON.parse(Ku.textContent);mr.base=`${new URL(mr.base,Ye())}`;function Ue(){return mr}function X(e){return mr.features.includes(e)}function Bt(e,t){return typeof t!="undefined"?mr.translations[e].replace("#",t.toString()):mr.translations[e]}function ht(e,t=document){return G(`[data-md-component=${e}]`,t)}function Ee(e,t=document){return P(`[data-md-component=${e}]`,t)}function Bu(e){let t=G(".md-typeset > :first-child",e);return b(t,"click",{once:!0}).pipe(f(()=>G(".md-typeset",e)),f(r=>({hash:__md_hash(r.innerHTML)})))}function ls(e){if(!X("announce.dismiss")||!e.childElementCount)return y;if(!e.hidden){let t=G(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return j(()=>{let t=new I;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),Bu(e).pipe($(r=>t.next(r)),V(()=>t.complete()),f(r=>H({ref:e},r)))})}function Yu(e,{target$:t}){return t.pipe(f(r=>({hidden:r!==e})))}function us(e,t){let r=new I;return r.subscribe(({hidden:n})=>{e.hidden=n}),Yu(e,t).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))}function To(e,t){return t==="inline"?A("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},A("div",{class:"md-tooltip__inner md-typeset"})):A("div",{class:"md-tooltip",id:e,role:"tooltip"},A("div",{class:"md-tooltip__inner md-typeset"}))}function On(...e){return A("div",{class:"md-tooltip2",role:"dialog"},A("div",{class:"md-tooltip2__inner md-typeset"},e))}function ps(...e){return A("div",{class:"md-tooltip2",role:"tooltip"},A("div",{class:"md-tooltip2__inner md-typeset"},e))}function fs(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return A("aside",{class:"md-annotation",tabIndex:0},To(t),A("a",{href:r,class:"md-annotation__index",tabIndex:-1},A("span",{"data-md-annotation-id":e})))}else return A("aside",{class:"md-annotation",tabIndex:0},To(t),A("span",{class:"md-annotation__index",tabIndex:-1},A("span",{"data-md-annotation-id":e})))}function ms(e){return A("button",{class:"md-code__button",title:Bt("clipboard.copy"),"data-clipboard-target":`#${e} > code`,"data-md-type":"copy"})}function ds(){return A("button",{class:"md-code__button",title:"Toggle line selection","data-md-type":"select"})}function hs(){return A("nav",{class:"md-code__nav"})}var Xu=_r(So());function bs(e){return A("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>A("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?Li(r):r)))}function Oo(e){let t=`tabbed-control tabbed-control--${e}`;return A("div",{class:t,hidden:!0},A("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function gs(e){return A("div",{class:"md-typeset__scrollwrap"},A("div",{class:"md-typeset__table"},e))}function Zu(e){var n;let t=Ue(),r=new URL(`../${e.version}/`,t.base);return A("li",{class:"md-version__item"},A("a",{href:`${r}`,class:"md-version__link"},e.title,((n=t.version)==null?void 0:n.alias)&&e.aliases.length>0&&A("span",{class:"md-version__alias"},e.aliases[0])))}function _s(e,t){var n;let r=Ue();return e=e.filter(o=>{var i;return!((i=o.properties)!=null&&i.hidden)}),A("div",{class:"md-version"},A("button",{class:"md-version__current","aria-label":Bt("select.version")},t.title,((n=r.version)==null?void 0:n.alias)&&t.aliases.length>0&&A("span",{class:"md-version__alias"},t.aliases[0])),A("ul",{class:"md-version__list"},e.map(Zu)))}var Qu=0;function ep(e,t=250){let r=re([ir(e),Ft(e,t)]).pipe(f(([o,i])=>o||i),ie()),n=j(()=>Ai(e)).pipe(oe(Ut),Lr(1),Ze(r),f(()=>Ci(e)));return r.pipe(Sr(o=>o),g(()=>re([r,n])),f(([o,i])=>({active:o,offset:i})),xe())}function Rr(e,t,r=250){let{content$:n,viewport$:o}=t,i=`__tooltip2_${Qu++}`;return j(()=>{let a=new I,s=new Un(!1);a.pipe(he(),ye(!1)).subscribe(s);let c=s.pipe(Tr(u=>Ve(+!u*250,Wn)),ie(),g(u=>u?n:y),$(u=>u.id=i),xe());re([a.pipe(f(({active:u})=>u)),c.pipe(g(u=>Ft(u,250)),J(!1))]).pipe(f(u=>u.some(p=>p))).subscribe(s);let l=s.pipe(L(u=>u),pe(c,o),f(([u,p,{size:d}])=>{let m=e.getBoundingClientRect(),h=m.width/2;if(p.role==="tooltip")return{x:h,y:8+m.height};if(m.y>=d.height/2){let{height:v}=Ae(p);return{x:h,y:-16-v}}else return{x:h,y:16+m.height}}));return re([c,a,l]).subscribe(([u,{offset:p},d])=>{u.style.setProperty("--md-tooltip-host-x",`${p.x}px`),u.style.setProperty("--md-tooltip-host-y",`${p.y}px`),u.style.setProperty("--md-tooltip-x",`${d.x}px`),u.style.setProperty("--md-tooltip-y",`${d.y}px`),u.classList.toggle("md-tooltip2--top",d.y<0),u.classList.toggle("md-tooltip2--bottom",d.y>=0)}),s.pipe(L(u=>u),pe(c,(u,p)=>p),L(u=>u.role==="tooltip")).subscribe(u=>{let p=Ae(G(":scope > *",u));u.style.setProperty("--md-tooltip-width",`${p.width}px`),u.style.setProperty("--md-tooltip-tail","0px")}),s.pipe(ie(),Ie(je),pe(c)).subscribe(([u,p])=>{p.classList.toggle("md-tooltip2--active",u)}),re([s.pipe(L(u=>u)),c]).subscribe(([u,p])=>{p.role==="dialog"?(e.setAttribute("aria-controls",i),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",i)}),s.pipe(L(u=>!u)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ep(e,r).pipe($(u=>a.next(u)),V(()=>a.complete()),f(u=>H({ref:e},u)))})}function Ge(e,{viewport$:t},r=document.body){return Rr(e,{content$:new U(n=>{let o=e.title,i=ps(o);return n.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",o)}}),viewport$:t},0)}function tp(e,t){let r=j(()=>re([Hi(e),Ut(t)])).pipe(f(([{x:n,y:o},i])=>{let{width:a,height:s}=Ae(e);return{x:n-i.x+a/2,y:o-i.y+s/2}}));return ir(e).pipe(g(n=>r.pipe(f(o=>({active:n,offset:o})),Me(+!n||1/0))))}function ys(e,t,{target$:r}){let[n,o]=Array.from(e.children);return j(()=>{let i=new I,a=i.pipe(he(),ye(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),Et(e).pipe(Q(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),R(i.pipe(L(({active:s})=>s)),i.pipe(Be(250),L(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(n):n.remove()},complete(){e.prepend(n)}}),i.pipe(Xe(16,je)).subscribe(({active:s})=>{n.classList.toggle("md-tooltip--active",s)}),i.pipe(Lr(125,je),L(()=>!!e.offsetParent),f(()=>e.offsetParent.getBoundingClientRect()),f(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),b(o,"click").pipe(Q(a),L(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),b(o,"mousedown").pipe(Q(a),pe(i)).subscribe(([s,{active:c}])=>{var l;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(c){s.preventDefault();let u=e.parentElement.closest(".md-annotation");u instanceof HTMLElement?u.focus():(l=xt())==null||l.blur()}}),r.pipe(Q(a),L(s=>s===n),It(125)).subscribe(()=>e.focus()),tp(e,t).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))})}function rp(e){let t=Ue();if(e.tagName!=="CODE")return[e];let r=[".c",".c1",".cm"];if(t.annotate){let n=e.closest("[class|=language]");if(n)for(let o of Array.from(n.classList)){if(!o.startsWith("language-"))continue;let[,i]=o.split("-");i in t.annotate&&r.push(...t.annotate[i])}}return P(r.join(", "),e)}function np(e){let t=[];for(let r of rp(e)){let n=[],o=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=o.nextNode();i;i=o.nextNode())n.push(i);for(let i of n){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,c]=a;if(typeof c=="undefined"){let l=i.splitText(a.index);i=l.splitText(s.length),t.push(l)}else{i.textContent=s,t.push(i);break}}}}return t}function xs(e,t){t.append(...Array.from(e.childNodes))}function Ln(e,t,{target$:r,print$:n}){let o=t.closest("[id]"),i=o==null?void 0:o.id,a=new Map;for(let s of np(t)){let[,c]=s.textContent.match(/\((\d+)\)/);Le(`:scope > li:nth-child(${c})`,e)&&(a.set(c,fs(c,i)),s.replaceWith(a.get(c)))}return a.size===0?y:j(()=>{let s=new I,c=s.pipe(he(),ye(!0)),l=[];for(let[u,p]of a)l.push([G(".md-typeset",p),G(`:scope > li:nth-child(${u})`,e)]);return n.pipe(Q(c)).subscribe(u=>{e.hidden=!u,e.classList.toggle("md-annotation-list",u);for(let[p,d]of l)u?xs(p,d):xs(d,p)}),R(...[...a].map(([,u])=>ys(u,t,{target$:r}))).pipe(V(()=>s.complete()),xe())})}function ws(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return ws(t)}}function Es(e,t){return j(()=>{let r=ws(e);return typeof r!="undefined"?Ln(r,e,t):y})}var Ss=_r(Mo());var op=0,Ts=R(b(window,"keydown").pipe(f(()=>!0)),R(b(window,"keyup"),b(window,"contextmenu")).pipe(f(()=>!1))).pipe(J(!1),se(1));function Os(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Os(t)}}function ip(e){return Re(e).pipe(f(({width:t})=>({scrollable:Mr(e).width>t})),fe("scrollable"))}function Ls(e,t){let{matches:r}=matchMedia("(hover)"),n=j(()=>{let o=new I,i=o.pipe(Gn(1));o.subscribe(({scrollable:m})=>{m&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[],s=e.closest("pre"),c=s.closest("[id]"),l=c?c.id:op++;s.id=`__code_${l}`;let u=[],p=e.closest(".highlight");if(p instanceof HTMLElement){let m=Os(p);if(typeof m!="undefined"&&(p.classList.contains("annotate")||X("content.code.annotate"))){let h=Ln(m,e,t);u.push(Re(p).pipe(Q(i),f(({width:v,height:x})=>v&&x),ie(),g(v=>v?h:y)))}}let d=P(":scope > span[id]",e);if(d.length&&(e.classList.add("md-code__content"),e.closest(".select")||X("content.code.select")&&!e.closest(".no-select"))){let m=+d[0].id.split("-").pop(),h=ds();a.push(h),X("content.tooltips")&&u.push(Ge(h,{viewport$}));let v=b(h,"click").pipe(Or(M=>!M,!1),$(()=>h.blur()),xe());v.subscribe(M=>{h.classList.toggle("md-code__button--active",M)});let x=me(d).pipe(oe(M=>Ft(M).pipe(f(O=>[M,O]))));v.pipe(g(M=>M?x:y)).subscribe(([M,O])=>{let N=Le(".hll.select",M);if(N&&!O)N.replaceWith(...Array.from(N.childNodes));else if(!N&&O){let ee=document.createElement("span");ee.className="hll select",ee.append(...Array.from(M.childNodes).slice(1)),M.append(ee)}});let w=me(d).pipe(oe(M=>b(M,"mousedown").pipe($(O=>O.preventDefault()),f(()=>M)))),E=v.pipe(g(M=>M?w:y),pe(Ts),f(([M,O])=>{var ee;let N=d.indexOf(M)+m;if(O===!1)return[N,N];{let le=P(".hll",e).map(ce=>d.indexOf(ce.parentElement)+m);return(ee=window.getSelection())==null||ee.removeAllRanges(),[Math.min(N,...le),Math.max(N,...le)]}})),_=_o(y).pipe(L(M=>M.startsWith(`__codelineno-${l}-`)));_.subscribe(M=>{let[,,O]=M.split("-"),N=O.split(":").map(le=>+le-m+1);N.length===1&&N.push(N[0]);for(let le of P(".hll:not(.select)",e))le.replaceWith(...Array.from(le.childNodes));let ee=d.slice(N[0]-1,N[1]);for(let le of ee){let ce=document.createElement("span");ce.className="hll",ce.append(...Array.from(le.childNodes).slice(1)),le.append(ce)}}),_.pipe(Me(1),Ie(ge)).subscribe(M=>{if(M.includes(":")){let O=document.getElementById(M.split(":")[0]);O&&setTimeout(()=>{let N=O,ee=-64;for(;N!==document.body;)ee+=N.offsetTop,N=N.offsetParent;window.scrollTo({top:ee})},1)}});let be=me(P('a[href^="#__codelineno"]',p)).pipe(oe(M=>b(M,"click").pipe($(O=>O.preventDefault()),f(()=>M)))).pipe(Q(i),pe(Ts),f(([M,O])=>{let ee=+G(`[id="${M.hash.slice(1)}"]`).parentElement.id.split("-").pop();if(O===!1)return[ee,ee];{let le=P(".hll",e).map(ce=>+ce.parentElement.id.split("-").pop());return[Math.min(ee,...le),Math.max(ee,...le)]}}));R(E,be).subscribe(M=>{let O=`#__codelineno-${l}-`;M[0]===M[1]?O+=M[0]:O+=`${M[0]}:${M[1]}`,history.replaceState({},"",O),window.dispatchEvent(new HashChangeEvent("hashchange",{newURL:window.location.origin+window.location.pathname+O,oldURL:window.location.href}))})}if(Ss.default.isSupported()&&(e.closest(".copy")||X("content.code.copy")&&!e.closest(".no-copy"))){let m=ms(s.id);a.push(m),X("content.tooltips")&&u.push(Ge(m,{viewport$}))}if(a.length){let m=hs();m.append(...a),s.insertBefore(m,e)}return ip(e).pipe($(m=>o.next(m)),V(()=>o.complete()),f(m=>H({ref:e},m)),Rt(R(...u).pipe(Q(i))))});return X("content.lazy")?Et(e).pipe(L(o=>o),Me(1),g(()=>n)):n}function ap(e,{target$:t,print$:r}){let n=!0;return R(t.pipe(f(o=>o.closest("details:not([open])")),L(o=>e===o),f(()=>({action:"open",reveal:!0}))),r.pipe(L(o=>o||!n),$(()=>n=e.open),f(o=>({action:o?"open":"close"}))))}function Ms(e,t){return j(()=>{let r=new I;return r.subscribe(({action:n,reveal:o})=>{e.toggleAttribute("open",n==="open"),o&&e.scrollIntoView()}),ap(e,t).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}var ks=0,As=new Map;function sp(e){let t=document.createElement("h3");t.innerHTML=e.innerHTML;let r=[t],n=e.nextElementSibling;for(;n&&!(n instanceof HTMLHeadingElement);)r.push(n.cloneNode(!0)),n=n.nextElementSibling;return r}function cp(e,t){for(let r of P("[href], [src]",e))for(let n of["href","src"]){let o=r.getAttribute(n);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){r[n]=new URL(r.getAttribute(n),t).toString();break}}for(let r of P("[name^=__], [for]",e))for(let n of["id","for","name"]){let o=r.getAttribute(n);o&&r.setAttribute(n,`${o}$preview_${ks}`)}return ks++,Y(e)}function lp(e){let t=As.get(e.toString());return t?Y(t):En(e).pipe(g(r=>cp(r,e)),f(r=>(As.set(e.toString(),r),r)))}function Cs(e,t){let{sitemap$:r}=t;if(!(e instanceof HTMLAnchorElement))return y;if(!(X("navigation.instant.preview")||e.hasAttribute("data-preview")))return y;e.removeAttribute("title");let n=re([ir(e),Ft(e).pipe(ke(1))]).pipe(f(([i,a])=>i||a),ie(),L(i=>i));return $t([r,n]).pipe(g(([i])=>{let a=new URL(e.href);return a.search=a.hash="",i.has(`${a}`)?Y(a):y}),g(i=>lp(i)),g(i=>{let a=e.hash?`article [id="${decodeURIComponent(e.hash.slice(1))}"]`:"article h1",s=Le(a,i);return typeof s=="undefined"?y:Y(sp(s))})).pipe(g(i=>{let a=new U(s=>{let c=On(...i);return s.next(c),document.body.append(c),()=>c.remove()});return Rr(e,H({content$:a},t))}))}var Hs=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs marker.marker.composition.class path,defs marker.marker.dependency.class path,defs marker.marker.extension.class path{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs marker.marker.aggregation.class path{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}[id^=entity] path,[id^=entity] rect{fill:var(--md-default-bg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs .marker.oneOrMore.er *,defs .marker.onlyOne.er *,defs .marker.zeroOrMore.er *,defs .marker.zeroOrOne.er *{stroke:var(--md-mermaid-edge-color)!important}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var ko,pp=0;function fp(){return typeof mermaid=="undefined"||mermaid instanceof Element?ar("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):Y(void 0)}function $s(e){return e.classList.remove("mermaid"),ko||(ko=fp().pipe($(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Hs,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),f(()=>{}),se(1))),ko.subscribe(()=>Uo(null,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${pp++}`,r=A("div",{class:"mermaid"}),n=e.textContent,{svg:o,fn:i}=yield mermaid.render(t,n),a=r.attachShadow({mode:"closed"});a.innerHTML=o,e.replaceWith(r),i==null||i(a)})),ko.pipe(f(()=>({ref:e})))}var Ps=A("table");function Is(e){return e.replaceWith(Ps),Ps.replaceWith(gs(e)),Y({ref:e})}function mp(e){let t=e.find(r=>r.checked)||e[0];return R(...e.map(r=>b(r,"change").pipe(f(()=>G(`label[for="${r.id}"]`))))).pipe(J(G(`label[for="${t.id}"]`)),f(r=>({active:r})))}function Rs(e,{viewport$:t,target$:r}){let n=G(".tabbed-labels",e),o=P(":scope > input",e),i=Oo("prev");e.append(i);let a=Oo("next");return e.append(a),j(()=>{let s=new I,c=s.pipe(he(),ye(!0));re([s,Re(e),Et(e)]).pipe(Q(c),Xe(1,je)).subscribe({next([{active:l},u]){let p=wt(l),{width:d}=Ae(l);e.style.setProperty("--md-indicator-x",`${p.x}px`),e.style.setProperty("--md-indicator-width",`${d}px`);let m=ln(n);(p.xm.x+u.width)&&n.scrollTo({left:Math.max(0,p.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),re([Ut(n),Re(n)]).pipe(Q(c)).subscribe(([l,u])=>{let p=Mr(n);i.hidden=l.x<16,a.hidden=l.x>p.width-u.width-16}),R(b(i,"click").pipe(f(()=>-1)),b(a,"click").pipe(f(()=>1))).pipe(Q(c)).subscribe(l=>{let{width:u}=Ae(n);n.scrollBy({left:u*l,behavior:"smooth"})}),r.pipe(Q(c),L(l=>o.includes(l))).subscribe(l=>l.click()),n.classList.add("tabbed-labels--linked");for(let l of o){let u=G(`label[for="${l.id}"]`);u.replaceChildren(A("a",{href:`#${u.htmlFor}`,tabIndex:-1},...Array.from(u.childNodes))),b(u.firstElementChild,"click").pipe(Q(c),L(p=>!(p.metaKey||p.ctrlKey)),$(p=>{p.preventDefault(),p.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${u.htmlFor}`),u.click()})}return X("content.tabs.link")&&s.pipe(ke(1),pe(t)).subscribe(([{active:l},{offset:u}])=>{let p=l.innerText.trim();if(l.hasAttribute("data-md-switching"))l.removeAttribute("data-md-switching");else{let d=e.offsetTop-u.y;for(let h of P("[data-tabs]"))for(let v of P(":scope > input",h)){let x=G(`label[for="${v.id}"]`);if(x!==l&&x.innerText.trim()===p){x.setAttribute("data-md-switching",""),v.click();break}}window.scrollTo({top:e.offsetTop-d});let m=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([p,...m])])}}),s.pipe(Q(c)).subscribe(()=>{for(let l of P("audio, video",e))l.offsetWidth&&l.autoplay?l.play().catch(()=>{}):l.pause()}),mp(o).pipe($(l=>s.next(l)),V(()=>s.complete()),f(l=>H({ref:e},l)))}).pipe(Ht(ge))}function js(e,t){let{viewport$:r,target$:n,print$:o}=t;return R(...P(".annotate:not(.highlight)",e).map(i=>Es(i,{target$:n,print$:o})),...P("pre:not(.mermaid) > code",e).map(i=>Ls(i,{target$:n,print$:o})),...P("a",e).map(i=>Cs(i,t)),...P("pre.mermaid",e).map(i=>$s(i)),...P("table:not([class])",e).map(i=>Is(i)),...P("details",e).map(i=>Ms(i,{target$:n,print$:o})),...P("[data-tabs]",e).map(i=>Rs(i,{viewport$:r,target$:n})),...P("[title]:not([data-preview])",e).filter(()=>X("content.tooltips")).map(i=>Ge(i,{viewport$:r})),...P(".footnote-ref",e).filter(()=>X("content.footnote.tooltips")).map(i=>Rr(i,{content$:new U(a=>{let s=new URL(i.href).hash.slice(1),c=Array.from(document.getElementById(s).cloneNode(!0).children),l=On(...c);return a.next(l),document.body.append(l),()=>l.remove()}),viewport$:r})))}function dp(e,{alert$:t}){return t.pipe(g(r=>R(Y(!0),Y(!1).pipe(It(2e3))).pipe(f(n=>({message:r,active:n})))))}function Fs(e,t){let r=G(".md-typeset",e);return j(()=>{let n=new I;return n.subscribe(({message:o,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=o}),dp(e,t).pipe($(o=>n.next(o)),V(()=>n.complete()),f(o=>H({ref:e},o)))})}function hp({viewport$:e}){if(!X("header.autohide"))return Y(!1);let t=e.pipe(f(({offset:{y:o}})=>o),Pt(2,1),f(([o,i])=>[oMath.abs(i-o.y)>100),f(([,[o]])=>o),ie()),n=Tn("search");return re([e,n]).pipe(f(([{offset:o},i])=>o.y>400&&!i),ie(),g(o=>o?r:Y(!1)),J(!1))}function Us(e,t){return j(()=>re([Re(e),hp(t)])).pipe(f(([{height:r},n])=>({height:r,hidden:n})),ie((r,n)=>r.height===n.height&&r.hidden===n.hidden),se(1))}function Ns(e,{viewport$:t,header$:r,main$:n}){return j(()=>{let o=new I,i=o.pipe(he(),ye(!0));o.pipe(fe("active"),Ze(r)).subscribe(([{active:s},{hidden:c}])=>{e.classList.toggle("md-header--shadow",s&&!c),e.hidden=c});let a=me(P("[title]",e)).pipe(L(()=>X("content.tooltips")),oe(s=>Ge(s,{viewport$:t})));return n.subscribe(o),r.pipe(Q(i),f(s=>H({ref:e},s)),Rt(a.pipe(Q(i))))})}function vp(e,{viewport$:t,header$:r}){return Sn(e,{viewport$:t,header$:r}).pipe(f(({offset:{y:n}})=>{let{height:o}=Ae(e);return{active:o>0&&n>=o}}),fe("active"))}function Ds(e,t){return j(()=>{let r=new I;r.subscribe({next({active:o}){e.classList.toggle("md-header__title--active",o)},complete(){e.classList.remove("md-header__title--active")}});let n=Le(".md-content h1");return typeof n=="undefined"?y:vp(n,t).pipe($(o=>r.next(o)),V(()=>r.complete()),f(o=>H({ref:e},o)))})}function Ws(e,{viewport$:t,header$:r}){let n=r.pipe(f(({height:i})=>i),ie()),o=n.pipe(g(()=>Re(e).pipe(f(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),fe("bottom"))));return re([n,o,t]).pipe(f(([i,{top:a,bottom:s},{offset:{y:c},size:{height:l}}])=>(l=Math.max(0,l-Math.max(0,a-c,i)-Math.max(0,l+c-s)),{offset:a-i,height:l,active:a-i<=c})),ie((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function bp(e){let t=__md_get("__palette")||{index:e.findIndex(n=>matchMedia(n.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return Y(...e).pipe(oe(n=>b(n,"change").pipe(f(()=>n))),J(e[r]),f(n=>({index:e.indexOf(n),color:{media:n.getAttribute("data-md-color-media"),scheme:n.getAttribute("data-md-color-scheme"),primary:n.getAttribute("data-md-color-primary"),accent:n.getAttribute("data-md-color-accent")}})),se(1))}function Vs(e){let t=P("input",e),r=A("meta",{name:"theme-color"});document.head.appendChild(r);let n=A("meta",{name:"color-scheme"});document.head.appendChild(n);let o=Ir("(prefers-color-scheme: light)");return j(()=>{let i=new I;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),c=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=c.getAttribute("data-md-color-scheme"),a.color.primary=c.getAttribute("data-md-color-primary"),a.color.accent=c.getAttribute("data-md-color-accent")}for(let[s,c]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,c);for(let s=0;sa.key==="Enter"),pe(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(f(()=>{let a=ht("header"),s=window.getComputedStyle(a);return n.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(c=>(+c).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(Ie(ge)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),bp(t).pipe(Q(o.pipe(ke(1))),jt(),$(a=>i.next(a)),V(()=>i.complete()),f(a=>H({ref:e},a)))})}function zs(e,{progress$:t}){return j(()=>{let r=new I;return r.subscribe(({value:n})=>{e.style.setProperty("--md-progress-value",`${n}`)}),t.pipe($(n=>r.next({value:n})),V(()=>r.complete()),f(n=>({ref:e,value:n})))})}var qs='.v u{text-decoration:underline!important;text-decoration-style:wavy!important;text-decoration-thickness:1px!important}.p{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-backdrop)/var(--alpha-lighter));cursor:pointer;height:100%;pointer-events:auto;position:absolute;transition:opacity .25s;width:100%}.p.m{opacity:0;pointer-events:none;transition:opacity .35s}.r{align-items:center;background-color:initial;border:none;border-radius:var(--space-2);cursor:pointer;display:flex;flex-shrink:0;font-family:var(--font-family);height:36px;justify-content:center;outline:none;padding:0;position:relative;transition:background-color .25s,color .25s;width:36px;z-index:1}.r svg{stroke:rgb(var(--color-foreground));height:18px;opacity:.5;width:18px}.r:before{background-color:rgb(var(--color-background-subtle));border-radius:var(--border-radius-2);content:"";inset:0;opacity:0;position:absolute;transform:scale(.75);transition:transform 125ms,opacity 125ms;z-index:0}.r:hover:before{opacity:1;transform:scale(1)}.r.c{cursor:auto}.r.c:before{display:none}.n{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-background)/var(--alpha-light));border-radius:var(--space-3);box-shadow:0 0 60px #0000000d;display:flex;height:480px;overflow:hidden;pointer-events:auto;position:absolute;transition:transform .25s cubic-bezier(.16,1,.3,1),opacity .25s;width:640px}.n.l{opacity:0;pointer-events:none;transform:scale(1.1);transition:transform .25s .15s,opacity .15s}@media (max-width:680px){.n{border-radius:0;height:100%;width:100%}}.u{display:flex;flex-basis:min-content;flex-direction:column;flex-grow:1;flex-shrink:0}@keyframes d{0%{transform:scale(0)}50%{transform:scale(1.2)}to{transform:scale(1)}}.y{animation:d .25s ease-in-out;background:var(--color-highlight);border-radius:100%;color:#fff;font-size:8px;font-weight:700;height:12px;padding-top:1px;position:absolute;right:4px;top:4px;width:12px}.i{background-color:rgb(var(--color-background-subtle)/var(--alpha-lighter));flex-shrink:0;overflow:scroll;position:relative;transition:width .35s cubic-bezier(.16,1,.3,1),opacity .25s;width:200px}.i>*{transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.i.l{opacity:0;width:0}.i.l>*{transform:translate(-48px)}@media (max-width:680px){.i{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-background-subtle)/var(--alpha-light));box-shadow:0 0 60px #00000026;height:100%;position:absolute;right:0;top:0}}.w{border-bottom:1px solid rgb(var(--color-foreground)/var(--alpha-lightest));display:flex;gap:var(--space-1);padding:var(--space-2)}.k{-webkit-overflow-scrolling:touch;overflow:auto;overscroll-behavior:contain}.z{padding:8px 10px}.X{color:rgb(var(--color-foreground)/var(--alpha-light));padding:var(--space-2);position:absolute;width:200px}.X,.j{display:flex;flex-direction:column}.j{gap:2px;list-style:none;padding:0}.F,.j{margin:0}.F{font-size:16px;font-weight:400}.F,.I{padding:8px}.I{font-size:14px;margin:4px 0 0;opacity:.5}.I,.o{font-size:12px}.o{cursor:pointer;display:flex;padding:4px 8px;position:relative}.o:before{background-color:var(--color-highlight-transparent);border-radius:var(--space-1);content:"";inset:0;opacity:0;position:absolute;transform:scale(.75);transition:transform 125ms,opacity 125ms;z-index:0}.o.g:before,.o:hover:before{opacity:1;transform:scale(1)}.o.g,.o:hover{color:var(--color-highlight)}.R{flex-grow:1}.R,.q{position:relative}.q{font-weight:700}.f{flex-grow:1}.f input{background:#0000;border:none;color:rgb(var(--color-foreground));font-family:var(--font-family);font-size:16px;height:100%;letter-spacing:-.25px;outline:none;width:100%}.b{color:rgb(var(--color-foreground)/var(--alpha-light));display:flex;flex-direction:column;gap:2px;line-height:1.3;list-style:none;margin:var(--space-2);margin-top:0;padding:0}.A,.b li{margin:0}.A{color:rgb(var(--color-foreground)/var(--alpha-lighter));font-size:12px;margin-top:var(--space-2);padding:0 18px}.a{border-radius:var(--space-2);color:inherit;cursor:pointer;display:flex;flex-direction:row;flex-grow:1;padding:8px 10px;position:relative;text-decoration:none}.a:before{background-color:rgb(var(--color-background-subtle));border-radius:var(--border-radius-2);content:"";display:block;inset:0;opacity:0;position:absolute;transform:scale(.9);transition:transform 125ms,opacity 125ms;z-index:0}@media (pointer:fine){.a.h:before,.a:hover:before{opacity:1;transform:scale(1)}}.a mark{background:#0000;color:var(--color-highlight)}.a u{background-color:var(--color-highlight-transparent);border-radius:2px;box-shadow:0 0 0 1px var(--color-highlight-transparent);text-decoration:none}.B{flex-grow:1}.s{margin-right:-8px;opacity:0;position:relative;transform:translate(-2px);transition:transform 125ms,opacity 125ms;z-index:0}@media (pointer:fine){.h>.s,:hover>.s{opacity:1;transform:none}}.x{font-size:14px;margin:0;position:relative}.x code{background:rgb(var(--color-background-subtle));border-radius:var(--space-1);font-size:13px;padding:2px 4px}.t{color:rgb(var(--color-foreground)/var(--alpha-lighter));display:inline-flex;flex-wrap:wrap;font-size:12px;gap:var(--space-1);list-style:none;margin:0;padding:0;position:relative}.t li{white-space:nowrap}.t li:after{content:"/";display:inline;margin-left:var(--space-1)}.t li:last-child:after{content:"";display:none}.e{--space-1:4px;--space-2:calc(var(--space-1)*2);--space-3:calc(var(--space-2)*2);--space-4:calc(var(--space-3)*2);--space-5:calc(var(--space-4)*2);--alpha-light:.7;--alpha-lighter:.54;--alpha-lightest:.1;--color-highlight:var(--md-accent-fg-color,#526cfe);--color-highlight-transparent:var(--md-accent-fg-color--transparent,#526cfe1a);--border-radius-1:var(--space-1);--border-radius-2:var(--space-2);--border-radius-3:calc(var(--space-1) + var(--space-2));--font-family:var(--md-text-font-family,Inter,Roboto Flex,system-ui,sans-serif);--font-size:16px;--line-height:1.5;--letter-spacing:-.5px;-webkit-font-smoothing:antialiased;align-items:center;display:flex;font-family:var(--font-family);font-size:var(--font-size);height:100vh;justify-content:center;letter-spacing:var(--letter-spacing);line-height:var(--line-height);pointer-events:none;position:absolute;width:100vw}@media (pointer:coarse){.e{height:-webkit-fill-available}}.e *,.e :after,.e :before{box-sizing:border-box}';function Ks(e,{index$:t}){let r=Ue(),n=document.createElement("div");document.body.appendChild(n),n.style.position="fixed",n.style.height="100%",n.style.top="0",n.style.zIndex="4";let o=n.attachShadow({mode:"open"});o.appendChild(A("style",{},qs.toString()));try{Ya(r.search,{highlight:r.features.includes("search.highlight")}),me(t).subscribe(i=>{for(let a of i.items)a.location=new URL(a.location,r.base).toString();Ja(i,o)}),b(e,"click").subscribe(()=>{go()}),Tn("search").pipe(ke(1)).subscribe(()=>go())}catch(i){e.hidden=!0;let a=G("label[for=__search]");a.hidden=!0}return Ke}var Bs=_r(So());function Ys(e,{index$:t,location$:r}){return re([t,r.pipe(J(Ye()),L(n=>!!n.searchParams.get("h")))]).pipe(f(([n,o])=>_p(n.config)(o.searchParams.get("h"))),f(n=>{var a;let o=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let c=s.textContent,l=n(c);l.length>c.length&&o.set(s,l)}for(let[s,c]of o){let{childNodes:l}=A("span",null,c);s.replaceWith(...Array.from(l))}return{ref:e,nodes:o}}))}function _p(e){let t=e.separator.split("|").map(o=>o.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":o).join("|"),r=new RegExp(t,"img"),n=(o,i,a)=>`${i}${a}`;return o=>{o=o.replace(/\s+/g," ").replace(/&/g,"&").trim();let i=new RegExp(`(^|${e.separator}|)(${o.split(r).map(a=>a.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&")).filter(a=>a.length>0).join("|")})`,"img");return a=>(0,Bs.default)(a).replace(i,n).replace(/<\/mark>(\s+)]*>/img,"$1")}}function yp(e,{viewport$:t,main$:r}){let n=e.closest(".md-grid"),o=n.offsetTop-n.parentElement.offsetTop;return re([r,t]).pipe(f(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(o,Math.max(0,s-i))-o,{height:a,locked:s>=i+o})),ie((i,a)=>i.height===a.height&&i.locked===a.locked))}function Ao(e,n){var o=n,{header$:t}=o,r=gr(o,["header$"]);let i=G(".md-sidebar__scrollwrap",e),{y:a}=wt(i);return j(()=>{let s=new I,c=s.pipe(he(),ye(!0)),l=s.pipe(Xe(0,je));return l.pipe(pe(t)).subscribe({next([{height:u},{height:p}]){i.style.height=`${u-2*a}px`,e.style.top=`${p}px`},complete(){i.style.height="",e.style.top=""}}),l.pipe(Sr()).subscribe(()=>{for(let u of P(".md-nav__link--active[href]",e)){if(!u.clientHeight)continue;let p=u.closest(".md-sidebar__scrollwrap");if(typeof p!="undefined"){let d=u.offsetTop-p.offsetTop,{height:m}=Ae(p);p.scrollTo({top:d-m/2})}}}),me(P("label[tabindex]",e)).pipe(oe(u=>b(u,"click").pipe(Ie(ge),f(()=>u),Q(c)))).subscribe(u=>{let p=G(`[id="${u.htmlFor}"]`);G(`[aria-labelledby="${u.id}"]`).setAttribute("aria-expanded",`${p.checked}`)}),X("content.tooltips")&&me(P("abbr[title]",e)).pipe(oe(u=>Ge(u,{viewport$})),Q(c)).subscribe(),yp(e,r).pipe($(u=>s.next(u)),V(()=>s.complete()),f(u=>H({ref:e},u)))})}function Gs(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return $t(et(`${r}/releases/latest`).pipe(_e(()=>y),f(n=>({version:n.tag_name})),ot({})),et(r).pipe(_e(()=>y),f(n=>({stars:n.stargazers_count,forks:n.forks_count})),ot({}))).pipe(f(([n,o])=>H(H({},n),o)))}else{let r=`https://api.github.com/users/${e}`;return et(r).pipe(f(n=>({repositories:n.public_repos})),ot({}))}}function Js(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return $t(et(`${r}/releases/permalink/latest`).pipe(_e(()=>y),f(({tag_name:n})=>({version:n})),ot({})),et(r).pipe(_e(()=>y),f(({star_count:n,forks_count:o})=>({stars:n,forks:o})),ot({}))).pipe(f(([n,o])=>H(H({},n),o)))}function Xs(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,n]=t;return Gs(r,n)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,n]=t;return Js(r,n)}return y}var xp;function wp(e){return xp||(xp=j(()=>{let t=__md_get("__source",sessionStorage);if(t)return Y(t);if(Ee("consent").length){let n=__md_get("__consent");if(!(n&&n.github))return y}return Xs(e.href).pipe($(n=>__md_set("__source",n,sessionStorage)))}).pipe(_e(()=>y),L(t=>Object.keys(t).length>0),f(t=>({facts:t})),se(1)))}function Zs(e){let t=G(":scope > :last-child",e);return j(()=>{let r=new I;return r.subscribe(({facts:n})=>{t.appendChild(bs(n)),t.classList.add("md-source__repository--active")}),wp(e).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}function Ep(e,{viewport$:t,header$:r}){return Re(document.body).pipe(g(()=>Sn(e,{header$:r,viewport$:t})),f(({offset:{y:n}})=>({hidden:n>=10})),fe("hidden"))}function Qs(e,t){return j(()=>{let r=new I;return r.subscribe({next({hidden:n}){e.hidden=n},complete(){e.hidden=!1}}),(X("navigation.tabs.sticky")?Y({hidden:!1}):Ep(e,t)).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}function Tp(e,{viewport$:t,header$:r}){let n=new Map,o=P(".md-nav__link",e);for(let s of o){let c=decodeURIComponent(s.hash.substring(1)),l=Le(`[id="${c}"]`);typeof l!="undefined"&&n.set(s,l)}let i=r.pipe(fe("height"),f(({height:s})=>{let c=ht("main"),l=G(":scope > :first-child",c);return s+.9*(l.offsetTop-c.offsetTop)}),xe());return Re(document.body).pipe(fe("height"),g(s=>j(()=>{let c=[];return Y([...n].reduce((l,[u,p])=>{for(;c.length&&n.get(c[c.length-1]).tagName>=p.tagName;)c.pop();let d=p.offsetTop;for(;!d&&p.parentElement;)p=p.parentElement,d=p.offsetTop;let m=p.offsetParent;for(;m;m=m.offsetParent)d+=m.offsetTop;return l.set([...c=[...c,u]].reverse(),d)},new Map))}).pipe(f(c=>new Map([...c].sort(([,l],[,u])=>l-u))),Ze(i),g(([c,l])=>t.pipe(Or(([u,p],{offset:{y:d},size:m})=>{let h=d+m.height>=Math.floor(s.height);for(;p.length;){let[,v]=p[0];if(v-l=d&&!h)p=[u.pop(),...p];else break}return[u,p]},[[],[...c]]),ie((u,p)=>u[0]===p[0]&&u[1]===p[1])))))).pipe(f(([s,c])=>({prev:s.map(([l])=>l),next:c.map(([l])=>l)})),J({prev:[],next:[]}),Pt(2,1),f(([s,c])=>s.prev.length{let i=new I,a=i.pipe(he(),ye(!0));if(i.subscribe(({prev:s,next:c})=>{for(let[l]of c)l.classList.remove("md-nav__link--passed"),l.classList.remove("md-nav__link--active");for(let[l,[u]]of s.entries())u.classList.add("md-nav__link--passed"),u.classList.toggle("md-nav__link--active",l===s.length-1)}),X("toc.follow")){let s=R(t.pipe(Be(1),f(()=>{})),t.pipe(Be(250),f(()=>"smooth")));i.pipe(L(({prev:c})=>c.length>0),Ze(n.pipe(Ie(ge))),pe(s)).subscribe(([[{prev:c}],l])=>{let[u]=c[c.length-1];if(u.offsetHeight){let p=ki(u);if(typeof p!="undefined"){let d=u.offsetTop-p.offsetTop,{height:m}=Ae(p);p.scrollTo({top:d-m/2,behavior:l})}}})}return X("navigation.tracking")&&t.pipe(Q(a),fe("offset"),Be(250),ke(1),Q(o.pipe(ke(1))),jt({delay:250}),pe(i)).subscribe(([,{prev:s}])=>{let c=Ye(),l=s[s.length-1];if(l&&l.length){let[u]=l,{hash:p}=new URL(u.href);c.hash!==p&&(c.hash=p,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),Tp(e,{viewport$:t,header$:r}).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))})}function Sp(e,{viewport$:t,main$:r,target$:n}){let o=t.pipe(f(({offset:{y:a}})=>a),Pt(2,1),f(([a,s])=>a>s&&s>0),ie()),i=r.pipe(f(({active:a})=>a));return re([i,o]).pipe(f(([a,s])=>!(a&&s)),ie(),Q(n.pipe(ke(1))),ye(!0),jt({delay:250}),f(a=>({hidden:a})))}function tc(e,{viewport$:t,header$:r,main$:n,target$:o}){let i=new I,a=i.pipe(he(),ye(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(Q(a),fe("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),b(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),Sp(e,{viewport$:t,main$:n,target$:o}).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))}function rc(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,t.port&&(e.port=t.port),e}function Op(e,t){let r=new Map;for(let n of P("url",e)){let o=G("loc",n),i=[rc(new URL(o.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",n)){let s=a.getAttribute("href");s!=null&&i.push(rc(new URL(s),t))}}return r}function dr(e){return ns(new URL("sitemap.xml",e)).pipe(f(t=>Op(t,new URL(e))),_e(()=>Y(new Map)),xe())}function nc({document$:e}){let t=new Map;e.pipe(g(()=>P("link[rel=alternate]")),f(r=>new URL(r.href)),L(r=>!t.has(r.toString())),oe(r=>dr(r).pipe(f(n=>[r,n]),_e(()=>y)))).subscribe(([r,n])=>{t.set(r.toString().replace(/\/$/,""),n)}),b(document.body,"click").pipe(L(r=>!r.metaKey&&!r.ctrlKey),g(r=>{if(r.target instanceof Element){let n=r.target.closest("a");if(n&&!n.target){let o=[...t].find(([p])=>n.href.startsWith(`${p}/`));if(typeof o=="undefined")return y;let[i,a]=o,s=Ye();if(s.href.startsWith(i))return y;let c=Ue(),l=s.href.replace(c.base,"");l=`${i}/${l}`;let u=a.has(l.split("#")[0])?new URL(l,c.base):new URL(i);return r.preventDefault(),Y(u)}}return y})).subscribe(r=>dt(r,!0))}var Co=_r(Mo());function Lp(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function oc({alert$:e}){Co.default.isSupported()&&new U(t=>{new Co.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||Lp(G(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe($(t=>{t.trigger.focus()}),f(()=>Bt("clipboard.copied"))).subscribe(e)}function ic(e,t){if(!(e.target instanceof Element))return y;let r=e.target.closest("a");if(r===null)return y;if(r.target||e.metaKey||e.ctrlKey)return y;let n=new URL(r.href);return n.search=n.hash="",t.has(`${n}`)?(e.preventDefault(),Y(r)):y}function ac(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function sc(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let n=t.getAttribute(r);if(n&&!/^(?:[a-z]+:)?\/\//i.test(n)){t[r]=t[r];break}}return Y(e)}function Mp(e){for(let n of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...X("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let o=Le(n),i=Le(n,e);typeof o!="undefined"&&typeof i!="undefined"&&o.replaceWith(i)}let t=ac(document);for(let[n,o]of ac(e))t.has(n)?t.delete(n):document.head.appendChild(o);for(let n of t.values()){let o=n.getAttribute("name");o!=="theme-color"&&o!=="color-scheme"&&n.remove()}let r=ht("container");return nt(P("script",r)).pipe(g(n=>{let o=e.createElement("script");if(n.src){for(let i of n.getAttributeNames())o.setAttribute(i,n.getAttribute(i));return n.replaceWith(o),new U(i=>{o.onload=()=>i.complete()})}else return o.textContent=n.textContent,n.replaceWith(o),y}),he(),ye(document))}function cc({sitemap$:e,location$:t,viewport$:r,progress$:n}){if(location.protocol==="file:")return Ke;Y(document).subscribe(sc);let o=b(document.body,"click").pipe(Ze(e),g(([s,c])=>ic(s,c)),f(({href:s})=>new URL(s)),xe()),i=b(window,"popstate").pipe(f(Ye),xe());o.pipe(pe(r)).subscribe(([s,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",s)}),R(o,i).subscribe(t);let a=t.pipe(fe("pathname"),g(s=>En(s,{progress$:n}).pipe(_e(()=>(dt(s,!0),y)))),g(sc),g(Mp),xe());return R(a.pipe(pe(t,(s,c)=>c)),a.pipe(g(()=>t),fe("hash")),t.pipe(ie((s,c)=>s.pathname===c.pathname&&s.hash===c.hash),g(()=>o),$(()=>history.back()))).subscribe(s=>{var c,l;history.state!==null||!s.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",es(s.hash),history.scrollRestoration="manual")}),t.subscribe(()=>{history.scrollRestoration="manual"}),b(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),r.pipe(fe("offset"),Be(100)).subscribe(({offset:s})=>{history.replaceState(s,"")}),X("navigation.instant.prefetch")&&R(b(document.body,"mousemove"),b(document.body,"focusin")).pipe(Ze(e),g(([s,c])=>ic(s,c)),Be(25),Yn(({href:s})=>s),cn(s=>{let c=document.createElement("link");return c.rel="prefetch",c.href=s.toString(),document.head.appendChild(c),b(c,"load").pipe(f(()=>c),Me(1))})).subscribe(s=>s.remove()),a}function lc(e){var u;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:n,currentBaseURL:o}=e,i=(u=Ho(o))==null?void 0:u.pathname;if(i===void 0)return;let a=kp(n.pathname,i);if(a===void 0)return;let s=Cp(t.keys());if(!t.has(s))return;let c=Ho(a,s);if(!c||!t.has(c.href))return;let l=Ho(a,r);if(l)return l.hash=n.hash,l.search=n.search,l}function Ho(e,t){try{return new URL(e,t)}catch(r){return}}function kp(e,t){if(e.startsWith(t))return e.slice(t.length)}function Ap(e,t){let r=Math.min(e.length,t.length),n;for(n=0;ny)),n=r.pipe(f(o=>{let[,i]=t.base.match(/([^/]+)\/?$/);return o.find(({version:a,aliases:s})=>a===i||s.includes(i))||o[0]}));r.pipe(f(o=>new Map(o.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),g(o=>b(document.body,"click").pipe(L(i=>!i.metaKey&&!i.ctrlKey),pe(n),g(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&o.has(s.href)){let c=s.href;return!i.target.closest(".md-version")&&o.get(c)===a?y:(i.preventDefault(),Y(new URL(c)))}}return y}),g(i=>dr(i).pipe(f(a=>{var s;return(s=lc({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:Ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(o=>dt(o,!0)),re([r,n]).subscribe(([o,i])=>{G(".md-header__topic").appendChild(_s(o,i))}),e.pipe(g(()=>n)).subscribe(o=>{var s;let i=new URL(t.base),a=__md_get("__outdated",sessionStorage,i);if(a===null){a=!0;let c=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(c)||(c=[c]);e:for(let l of c)for(let u of o.aliases.concat(o.version))if(new RegExp(l,"i").test(u)){a=!1;break e}__md_set("__outdated",a,sessionStorage,i)}if(a)for(let c of Ee("outdated"))c.hidden=!1})}function pc({document$:e,viewport$:t}){e.pipe(g(()=>P(".md-ellipsis")),oe(r=>Et(r).pipe(Q(e.pipe(ke(1))),L(n=>n),f(()=>r),Me(1))),L(r=>r.offsetWidth{let n=r.innerText,o=r.closest("a")||r;return o.title=n,X("content.tooltips")?Ge(o,{viewport$:t}).pipe(Q(e.pipe(ke(1))),V(()=>o.removeAttribute("title"))):y})).subscribe(),X("content.tooltips")&&e.pipe(g(()=>P(".md-status")),oe(r=>Ge(r,{viewport$:t}))).subscribe()}function fc({document$:e,tablet$:t}){e.pipe(g(()=>P(".md-toggle--indeterminate")),$(r=>{r.indeterminate=!0,r.checked=!1}),oe(r=>b(r,"change").pipe(Xn(()=>r.classList.contains("md-toggle--indeterminate")),f(()=>r))),pe(t)).subscribe(([r,n])=>{r.classList.remove("md-toggle--indeterminate"),n&&(r.checked=!1)})}function Hp(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function mc({document$:e}){e.pipe(g(()=>P("[data-md-scrollfix]")),$(t=>t.removeAttribute("data-md-scrollfix")),L(Hp),oe(t=>b(t,"touchstart").pipe(f(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let n=e[r];typeof n=="string"?n=document.createTextNode(n):n.parentNode&&n.parentNode.removeChild(n),r?t.insertBefore(this.previousSibling,n):t.replaceChild(n,this)}}}));function $p(){return location.protocol==="file:"?ar(`${new URL("search.js",Mn.base)}`).pipe(f(()=>__index),_e(()=>Ke),se(1)):et(new URL("search.json",Mn.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var vt=Si(),Ur=Za(),hr=ts(Ur),hc=Xa(),ze=cs(),$o=Ir("(min-width: 60em)"),vc=Ir("(min-width: 76.25em)"),bc=rs(),Mn=Ue(),gc=Le(".md-search")?$p():Ke,Po=new I;oc({alert$:Po});nc({document$:vt});var Io=new I,_c=dr(Mn.base);X("navigation.instant")&&cc({sitemap$:_c,location$:Ur,viewport$:ze,progress$:Io}).subscribe(vt);var dc;((dc=Mn.version)==null?void 0:dc.provider)==="mike"&&uc({document$:vt});R(Ur,hr).pipe(It(125)).subscribe(()=>{Eo("drawer",!1),Eo("search",!1)});hc.pipe(L(({mode:e,meta:t})=>e==="global"&&!t)).subscribe(e=>{switch(e.type){case",":case"p":let t=document.querySelector("link[rel=prev]");t instanceof HTMLLinkElement&&dt(t);break;case".":case"n":let r=document.querySelector("link[rel=next]");r instanceof HTMLLinkElement&&dt(r);break;case"/":let n=document.querySelector("[data-md-component=search] button");n instanceof HTMLButtonElement&&n.click();break;case"Enter":let o=xt();o instanceof HTMLLabelElement&&o.click()}});pc({viewport$:ze,document$:vt});fc({document$:vt,tablet$:$o});mc({document$:vt});var Lt=Us(ht("header"),{viewport$:ze}),Fr=vt.pipe(f(()=>ht("main")),g(e=>Ws(e,{viewport$:ze,header$:Lt})),se(1)),Pp=R(...Ee("consent").map(e=>us(e,{target$:hr})),...Ee("dialog").map(e=>Fs(e,{alert$:Po})),...Ee("palette").map(e=>Vs(e)),...Ee("progress").map(e=>zs(e,{progress$:Io})),...Ee("search").map(e=>Ks(e,{index$:gc})),...Ee("source").map(e=>Zs(e))),Ip=j(()=>R(...Ee("announce").map(e=>ls(e)),...Ee("content").map(e=>js(e,{sitemap$:_c,viewport$:ze,target$:hr,print$:bc})),...Ee("content").map(e=>X("search.highlight")?Ys(e,{index$:gc,location$:Ur}):y),...Ee("header").map(e=>Ns(e,{viewport$:ze,header$:Lt,main$:Fr})),...Ee("header-title").map(e=>Ds(e,{viewport$:ze,header$:Lt})),...Ee("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?yo(vc,()=>Ao(e,{viewport$:ze,header$:Lt,main$:Fr})):yo($o,()=>Ao(e,{viewport$:ze,header$:Lt,main$:Fr}))),...Ee("tabs").map(e=>Qs(e,{viewport$:ze,header$:Lt})),...Ee("toc").map(e=>ec(e,{viewport$:ze,header$:Lt,main$:Fr,target$:hr})),...Ee("top").map(e=>tc(e,{viewport$:ze,header$:Lt,main$:Fr,target$:hr})))),yc=vt.pipe(g(()=>Ip),Rt(Pp),se(1));yc.subscribe();window.document$=vt;window.location$=Ur;window.target$=hr;window.keyboard$=hc;window.viewport$=ze;window.tablet$=$o;window.screen$=vc;window.print$=bc;window.alert$=Po;window.progress$=Io;window.component$=yc;})(); -/*! update cache: 20260331044513 */ +/*! update cache: 20260331053206 */ diff --git a/en/chapter_stack_and_queue/queue/index.html b/en/chapter_stack_and_queue/queue/index.html index 104248652..52648b100 100644 --- a/en/chapter_stack_and_queue/queue/index.html +++ b/en/chapter_stack_and_queue/queue/index.html @@ -6441,7 +6441,7 @@ typedef struct { int *nums; // Array for storing queue elements int front; // Front pointer, points to the front of the queue element - int queSize; // Rear pointer, points to rear + 1 + int queSize; // Current number of elements in the queue int queCapacity; // Queue capacity } ArrayQueue; diff --git a/en/javascripts/katex.js b/en/javascripts/katex.js index eff15a0d4..3a9f13b68 100644 --- a/en/javascripts/katex.js +++ b/en/javascripts/katex.js @@ -8,4 +8,4 @@ document$.subscribe(({ body }) => { ], }); }); -/*! update cache: 20260331044513 */ +/*! update cache: 20260331053206 */ diff --git a/en/javascripts/mathjax.js b/en/javascripts/mathjax.js index 312339a55..1b73758e5 100644 --- a/en/javascripts/mathjax.js +++ b/en/javascripts/mathjax.js @@ -15,4 +15,4 @@ window.MathJax = { document$.subscribe(() => { MathJax.typesetPromise(); }); -/*! update cache: 20260331044513 */ +/*! update cache: 20260331053206 */ diff --git a/en/javascripts/starfield.js b/en/javascripts/starfield.js index 3df115a59..b2ae3c56e 100644 --- a/en/javascripts/starfield.js +++ b/en/javascripts/starfield.js @@ -469,4 +469,4 @@ return Starfield; }); -/*! update cache: 20260331044513 */ +/*! update cache: 20260331053206 */ diff --git a/en/search.json b/en/search.json index 45aca1f8b..1ec805970 100644 --- a/en/search.json +++ b/en/search.json @@ -1 +1 @@ -{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"chapter_appendix/","level":1,"title":"Chapter 16.   Appendix","text":"","path":["Chapter 16. Appendix","Chapter 16.   Appendix"],"tags":[]},{"location":"chapter_appendix/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 16.1   Programming Environment Installation
  • 16.2   Contributing Together
  • 16.3   Terminology Table
","path":["Chapter 16. Appendix","Chapter 16.   Appendix"],"tags":[]},{"location":"chapter_appendix/contribution/","level":1,"title":"16.2   Contributing Together","text":"

Due to limited capacity, there may be inevitable omissions and errors in this book. We appreciate your understanding and are grateful for your help in correcting them. If you discover typos, broken links, missing content, ambiguous wording, unclear explanations, or structural issues, please help us make corrections to provide readers with higher-quality learning resources.

The GitHub IDs of all contributors will be displayed on the homepage of the book repository, the web version, and the PDF version to acknowledge their selfless contributions to the open source community.

The Charm of Open Source

The interval between two printings of a physical book is often quite long, making content updates very inconvenient.

In this open source book, the time for content updates has been shortened to just days or even hours.

","path":["Chapter 16. Appendix","16.2   Contributing Together"],"tags":[]},{"location":"chapter_appendix/contribution/#1-minor-content-adjustments","level":3,"title":"1.   Minor Content Adjustments","text":"

As shown in Figure 16-3, there is an \"edit icon\" in the top-right corner of each page. You can modify text or code by following these steps.

  1. Click the \"edit icon\". If you encounter a prompt asking you to \"Fork this repository\", please approve the operation.
  2. Modify the content of the Markdown source file, verify the correctness of the content, and maintain consistent formatting as much as possible.
  3. Fill in a description of your changes at the bottom of the page, then click the \"Propose file change\" button. After the page transitions, click the \"Create pull request\" button to submit your pull request.

Figure 16-3   Page edit button

Images cannot be directly modified. Please describe the issue by creating a new Issue or leaving a comment. We will promptly redraw and replace the images.

","path":["Chapter 16. Appendix","16.2   Contributing Together"],"tags":[]},{"location":"chapter_appendix/contribution/#2-content-creation","level":3,"title":"2.   Content Creation","text":"

If you are interested in contributing to this open source project, including translating code into other programming languages or expanding article content, you will need to follow the Pull Request workflow below.

  1. Log in to GitHub and Fork the book's code repository to your personal account.
  2. Enter your forked repository webpage and use the git clone command to clone the repository to your local machine.
  3. Create content locally and conduct comprehensive tests to verify code correctness.
  4. Commit your local changes and push them to the remote repository.
  5. Refresh the repository webpage and click the \"Create pull request\" button to submit your pull request.
","path":["Chapter 16. Appendix","16.2   Contributing Together"],"tags":[]},{"location":"chapter_appendix/contribution/#3-docker-deployment","level":3,"title":"3.   Docker Deployment","text":"

From the root directory of hello-algo, run the following Docker script to access the project at http://localhost:8000:

docker-compose up -d\n

Use the following command to remove the deployment:

docker-compose down\n
","path":["Chapter 16. Appendix","16.2   Contributing Together"],"tags":[]},{"location":"chapter_appendix/installation/","level":1,"title":"16.1   Programming Environment Installation","text":"","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#1611-installing-ide","level":2,"title":"16.1.1   Installing Ide","text":"

We recommend using the open-source and lightweight VS Code as the local integrated development environment (IDE). Visit the VS Code official website, and download and install the appropriate version of VS Code according to your operating system.

Figure 16-1   Download VS Code from the Official Website

VS Code has a powerful ecosystem of extensions that supports running and debugging most programming languages. For example, after installing the \"Python Extension Pack\" extension, you can debug Python code. The installation steps are shown in the following figure.

Figure 16-2   Install VS Code Extensions

","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#1612-installing-language-environments","level":2,"title":"16.1.2   Installing Language Environments","text":"","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#1-python-environment","level":3,"title":"1.   Python Environment","text":"
  1. Download and install Miniconda3, which requires Python 3.10 or newer.
  2. Search for python in the VS Code extension marketplace and install the Python Extension Pack.
  3. (Optional) Enter pip install black on the command line to install the code formatter.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#2-cc-environment","level":3,"title":"2.   C/c++ Environment","text":"
  1. Windows systems need to install MinGW (configuration tutorial); macOS comes with Clang built-in and does not require installation.
  2. Search for c++ in the VS Code extension marketplace and install the C/C++ Extension Pack.
  3. (Optional) Open the Settings page, search for the Clang_format_fallback Style code formatting option, and set it to { BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#3-java-environment","level":3,"title":"3.   Java Environment","text":"
  1. Download and install OpenJDK (version must be > JDK 9).
  2. Search for java in the VS Code extension marketplace and install the Extension Pack for Java.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#4-c-environment","level":3,"title":"4.   C# Environment","text":"
  1. Download and install .Net 8.0.
  2. Search for C# Dev Kit in the VS Code extension marketplace and install C# Dev Kit (configuration tutorial).
  3. You can also use Visual Studio (installation tutorial).
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#5-go-environment","level":3,"title":"5.   Go Environment","text":"
  1. Download and install Go.
  2. Search for go in the VS Code extension marketplace and install Go.
  3. Press Ctrl + Shift + P to open the command palette, type go, select Go: Install/Update Tools, check all options and install.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#6-swift-environment","level":3,"title":"6.   Swift Environment","text":"
  1. Download and install Swift.
  2. Search for swift in the VS Code extension marketplace and install Swift for Visual Studio Code.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#7-javascript-environment","level":3,"title":"7.   Javascript Environment","text":"
  1. Download and install Node.js.
  2. (Optional) Search for Prettier in the VS Code extension marketplace and install the code formatter.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#8-typescript-environment","level":3,"title":"8.   Typescript Environment","text":"
  1. Follow the same installation steps as the JavaScript environment.
  2. Install TypeScript Execute (tsx).
  3. Search for typescript in the VS Code extension marketplace and install Pretty TypeScript Errors.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#9-dart-environment","level":3,"title":"9.   Dart Environment","text":"
  1. Download and install Dart.
  2. Search for dart in the VS Code extension marketplace and install Dart.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#10-rust-environment","level":3,"title":"10.   Rust Environment","text":"
  1. Download and install Rust.
  2. Search for rust in the VS Code extension marketplace and install rust-analyzer.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/terminology/","level":1,"title":"16.3   Terminology Table","text":"

The following table lists important terms that appear in this book. It is worth noting the following points:

  • We recommend remembering the English names of terms to help with reading English literature.
  • Some terms have different names in Simplified Chinese and Traditional Chinese.

Table 16-1   Important Terms in Data Structures and Algorithms

English Simplified Chinese Traditional Chinese algorithm 算法 演算法 data structure 数据结构 資料結構 code 代码 程式碼 file 文件 檔案 function 函数 函式 method 方法 方法 variable 变量 變數 asymptotic complexity analysis 渐近复杂度分析 漸近複雜度分析 time complexity 时间复杂度 時間複雜度 space complexity 空间复杂度 空間複雜度 loop 循环 迴圈 iteration 迭代 迭代 recursion 递归 遞迴 tail recursion 尾递归 尾遞迴 recursion tree 递归树 遞迴樹 big-\\(O\\) notation 大 \\(O\\) 记号 大 \\(O\\) 記號 asymptotic upper bound 渐近上界 漸近上界 sign-magnitude 原码 原碼 1’s complement 反码 一補數 2’s complement 补码 二補數 array 数组 陣列 index 索引 索引 linked list 链表 鏈結串列 linked list node, list node 链表节点 鏈結串列節點 head node 头节点 頭節點 tail node 尾节点 尾節點 list 列表 串列 dynamic array 动态数组 動態陣列 hard disk 硬盘 硬碟 random-access memory (RAM) 内存 記憶體 cache memory 缓存 快取 cache miss 缓存未命中 快取未命中 cache hit rate 缓存命中率 快取命中率 stack 栈 堆疊 top of the stack 栈顶 堆疊頂 bottom of the stack 栈底 堆疊底 queue 队列 佇列 double-ended queue 双向队列 雙向佇列 front of the queue 队首 佇列首 rear of the queue 队尾 佇列尾 hash table 哈希表 雜湊表 hash set 哈希集合 雜湊集合 bucket 桶 桶 hash function 哈希函数 雜湊函式 hash collision 哈希冲突 雜湊衝突 load factor 负载因子 負載因子 separate chaining 链式地址 鏈結位址 open addressing 开放寻址 開放定址 linear probing 线性探测 線性探查 lazy deletion 懒删除 懶刪除 binary tree 二叉树 二元樹 tree node 树节点 樹節點 left-child node 左子节点 左子節點 right-child node 右子节点 右子節點 parent node 父节点 父節點 left subtree 左子树 左子樹 right subtree 右子树 右子樹 root node 根节点 根節點 leaf node 叶节点 葉節點 edge 边 邊 level 层 層 degree 度 度 height 高度 高度 depth 深度 深度 perfect binary tree 完美二叉树 完美二元樹 complete binary tree 完全二叉树 完全二元樹 full binary tree 完满二叉树 完滿二元樹 balanced binary tree 平衡二叉树 平衡二元樹 binary search tree 二叉搜索树 二元搜尋樹 AVL tree AVL 树 AVL 樹 red-black tree 红黑树 紅黑樹 level-order traversal 层序遍历 層序走訪 breadth-first traversal 广度优先遍历 廣度優先走訪 depth-first traversal 深度优先遍历 深度優先走訪 binary search tree 二叉搜索树 二元搜尋樹 balanced binary search tree 平衡二叉搜索树 平衡二元搜尋樹 balance factor 平衡因子 平衡因子 heap 堆 堆積 max heap 大顶堆 大頂堆積 min heap 小顶堆 小頂堆積 priority queue 优先队列 優先佇列 heapify 堆化 堆積化 top-\\(k\\) problem Top-\\(k\\) 问题 Top-\\(k\\) 問題 graph 图 圖 vertex 顶点 頂點 undirected graph 无向图 無向圖 directed graph 有向图 有向圖 connected graph 连通图 連通圖 disconnected graph 非连通图 非連通圖 weighted graph 有权图 有權圖 adjacency 邻接 鄰接 path 路径 路徑 in-degree 入度 入度 out-degree 出度 出度 adjacency matrix 邻接矩阵 鄰接矩陣 adjacency list 邻接表 鄰接表 breadth-first search 广度优先搜索 廣度優先搜尋 depth-first search 深度优先搜索 深度優先搜尋 binary search 二分查找 二分搜尋 searching algorithm 搜索算法 搜尋演算法 sorting algorithm 排序算法 排序演算法 selection sort 选择排序 選擇排序 bubble sort 冒泡排序 泡沫排序 insertion sort 插入排序 插入排序 quick sort 快速排序 快速排序 merge sort 归并排序 合併排序 heap sort 堆排序 堆積排序 bucket sort 桶排序 桶排序 counting sort 计数排序 計數排序 radix sort 基数排序 基數排序 divide and conquer 分治 分治 hanota problem 汉诺塔问题 河內塔問題 backtracking algorithm 回溯算法 回溯演算法 constraint 约束 約束 solution 解 解 state 状态 狀態 pruning 剪枝 剪枝 permutations problem 全排列问题 全排列問題 subset-sum problem 子集和问题 子集合問題 \\(n\\)-queens problem \\(n\\) 皇后问题 \\(n\\) 皇后問題 dynamic programming 动态规划 動態規劃 initial state 初始状态 初始狀態 state-transition equation 状态转移方程 狀態轉移方程 knapsack problem 背包问题 背包問題 edit distance problem 编辑距离问题 編輯距離問題 greedy algorithm 贪心算法 貪婪演算法","path":["Chapter 16. Appendix","16.3   Terminology Table"],"tags":[]},{"location":"chapter_array_and_linkedlist/","level":1,"title":"Chapter 4.   Array and Linked List","text":"

Abstract

The world of data structures is like a solid brick wall.

Array bricks are neatly arranged, tightly packed one by one. Linked list bricks are scattered everywhere, with connecting vines freely weaving through the gaps between bricks.

","path":["Chapter 4. Array and Linked List","Chapter 4.   Array and Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 4.1   Array
  • 4.2   Linked List
  • 4.3   List
  • 4.4   Memory and Cache *
  • 4.5   Summary
","path":["Chapter 4. Array and Linked List","Chapter 4.   Array and Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/","level":1,"title":"4.1   Array","text":"

An array is a linear data structure that stores elements of the same type in contiguous memory space. The position of an element in the array is called the element's index. Figure 4-1 illustrates the main concepts and storage method of arrays.

Figure 4-1   Array definition and storage method

","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#411-common-array-operations","level":2,"title":"4.1.1   Common Array Operations","text":"","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#1-initializing-arrays","level":3,"title":"1.   Initializing Arrays","text":"

We can choose between two array initialization methods based on our needs: without initial values or with given initial values. When no initial values are specified, most programming languages will initialize array elements to \\(0\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
# Initialize array\narr: list[int] = [0] * 5  # [ 0, 0, 0, 0, 0 ]\nnums: list[int] = [1, 3, 2, 5, 4]\n
array.cpp
/* Initialize array */\n// Stored on stack\nint arr[5];\nint nums[5] = { 1, 3, 2, 5, 4 };\n// Stored on heap (requires manual memory release)\nint* arr1 = new int[5];\nint* nums1 = new int[5] { 1, 3, 2, 5, 4 };\n
array.java
/* Initialize array */\nint[] arr = new int[5]; // { 0, 0, 0, 0, 0 }\nint[] nums = { 1, 3, 2, 5, 4 };\n
array.cs
/* Initialize array */\nint[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ]\nint[] nums = [1, 3, 2, 5, 4];\n
array.go
/* Initialize array */\nvar arr [5]int\n// In Go, specifying length ([5]int) creates an array; not specifying length ([]int) creates a slice\n// Since Go's arrays are designed to have their length determined at compile time, only constants can be used to specify the length\n// For convenience in implementing the extend() method, slices are treated as arrays below\nnums := []int{1, 3, 2, 5, 4}\n
array.swift
/* Initialize array */\nlet arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]\nlet nums = [1, 3, 2, 5, 4]\n
array.js
/* Initialize array */\nvar arr = new Array(5).fill(0);\nvar nums = [1, 3, 2, 5, 4];\n
array.ts
/* Initialize array */\nlet arr: number[] = new Array(5).fill(0);\nlet nums: number[] = [1, 3, 2, 5, 4];\n
array.dart
/* Initialize array */\nList<int> arr = List.filled(5, 0); // [0, 0, 0, 0, 0]\nList<int> nums = [1, 3, 2, 5, 4];\n
array.rs
/* Initialize array */\nlet arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0]\nlet slice: &[i32] = &[0; 5];\n// In Rust, specifying length ([i32; 5]) creates an array; not specifying length (&[i32]) creates a slice\n// Since Rust's arrays are designed to have their length determined at compile time, only constants can be used to specify the length\n// Vector is the type generally used as a dynamic array in Rust\n// For convenience in implementing the extend() method, vectors are treated as arrays below\nlet nums: Vec<i32> = vec![1, 3, 2, 5, 4];\n
array.c
/* Initialize array */\nint arr[5] = { 0 }; // { 0, 0, 0, 0, 0 }\nint nums[5] = { 1, 3, 2, 5, 4 };\n
array.kt
/* Initialize array */\nvar arr = IntArray(5) // { 0, 0, 0, 0, 0 }\nvar nums = intArrayOf(1, 3, 2, 5, 4)\n
array.rb
# Initialize array\narr = Array.new(5, 0)\nnums = [1, 3, 2, 5, 4]\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#2-accessing-elements","level":3,"title":"2.   Accessing Elements","text":"

Array elements are stored in contiguous memory space, which means calculating the memory address of array elements is very easy. Given the array's memory address (the memory address of the first element) and an element's index, we can use the formula shown in Figure 4-2 to calculate the element's memory address and directly access that element.

Figure 4-2   Memory address calculation for array elements

Observing Figure 4-2, we find that the first element of an array has an index of \\(0\\), which may seem counterintuitive since counting from \\(1\\) would be more natural. However, from the perspective of the address calculation formula, an index is essentially an offset from the memory address. The address offset of the first element is \\(0\\), so it is reasonable for its index to be \\(0\\).

Accessing elements in an array is highly efficient; we can randomly access any element in the array in \\(O(1)\\) time.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def random_access(nums: list[int]) -> int:\n    \"\"\"Random access to element\"\"\"\n    # Randomly select a number from the interval [0, len(nums)-1]\n    random_index = random.randint(0, len(nums) - 1)\n    # Retrieve and return the random element\n    random_num = nums[random_index]\n    return random_num\n
array.cpp
/* Random access to element */\nint randomAccess(int *nums, int size) {\n    // Randomly select a number from interval [0, size)\n    int randomIndex = rand() % size;\n    // Retrieve and return the random element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
array.java
/* Random access to element */\nint randomAccess(int[] nums) {\n    // Randomly select a number in the interval [0, nums.length)\n    int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length);\n    // Retrieve and return the random element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
array.cs
/* Random access to element */\nint RandomAccess(int[] nums) {\n    Random random = new();\n    // Randomly select a number in interval [0, nums.Length)\n    int randomIndex = random.Next(nums.Length);\n    // Retrieve and return the random element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
array.go
/* Random access to element */\nfunc randomAccess(nums []int) (randomNum int) {\n    // Randomly select a number in the interval [0, nums.length)\n    randomIndex := rand.Intn(len(nums))\n    // Retrieve and return the random element\n    randomNum = nums[randomIndex]\n    return\n}\n
array.swift
/* Random access to element */\nfunc randomAccess(nums: [Int]) -> Int {\n    // Randomly select a number in interval [0, nums.count)\n    let randomIndex = nums.indices.randomElement()!\n    // Retrieve and return the random element\n    let randomNum = nums[randomIndex]\n    return randomNum\n}\n
array.js
/* Random access to element */\nfunction randomAccess(nums) {\n    // Randomly select a number in the interval [0, nums.length)\n    const random_index = Math.floor(Math.random() * nums.length);\n    // Retrieve and return the random element\n    const random_num = nums[random_index];\n    return random_num;\n}\n
array.ts
/* Random access to element */\nfunction randomAccess(nums: number[]): number {\n    // Randomly select a number in the interval [0, nums.length)\n    const random_index = Math.floor(Math.random() * nums.length);\n    // Retrieve and return the random element\n    const random_num = nums[random_index];\n    return random_num;\n}\n
array.dart
/* Random access to element */\nint randomAccess(List<int> nums) {\n  // Randomly select a number in the interval [0, nums.length)\n  int randomIndex = Random().nextInt(nums.length);\n  // Retrieve and return the random element\n  int randomNum = nums[randomIndex];\n  return randomNum;\n}\n
array.rs
/* Random access to element */\nfn random_access(nums: &[i32]) -> i32 {\n    // Randomly select a number in interval [0, nums.len())\n    let random_index = rand::thread_rng().gen_range(0..nums.len());\n    // Retrieve and return the random element\n    let random_num = nums[random_index];\n    random_num\n}\n
array.c
/* Random access to element */\nint randomAccess(int *nums, int size) {\n    // Randomly select a number from interval [0, size)\n    int randomIndex = rand() % size;\n    // Retrieve and return the random element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
array.kt
/* Random access to element */\nfun randomAccess(nums: IntArray): Int {\n    // Randomly select a number in interval [0, nums.size)\n    val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size)\n    // Retrieve and return the random element\n    val randomNum = nums[randomIndex]\n    return randomNum\n}\n
array.rb
### Random access element ###\ndef random_access(nums)\n  # Randomly select a number in the interval [0, nums.length)\n  random_index = Random.rand(0...nums.length)\n\n  # Retrieve and return the random element\n  nums[random_index]\nend\n
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#3-inserting-elements","level":3,"title":"3.   Inserting Elements","text":"

Array elements are stored \"tightly adjacent\" in memory, with no space between them to store any additional data. As shown in Figure 4-3, if we want to insert an element in the middle of an array, we need to shift all elements after that position backward by one position, and then assign the value to that index.

Figure 4-3   Example of inserting an element into an array

It is worth noting that since the length of an array is fixed, inserting an element will inevitably cause the element at the end of the array to be \"lost\". We will leave the solution to this problem for discussion in the \"List\" chapter.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def insert(nums: list[int], num: int, index: int):\n    \"\"\"Insert element num at index index in the array\"\"\"\n    # Move all elements at and after index index backward by one position\n    for i in range(len(nums) - 1, index, -1):\n        nums[i] = nums[i - 1]\n    # Assign num to the element at index index\n    nums[index] = num\n
array.cpp
/* Insert element num at index index in the array */\nvoid insert(int *nums, int size, int num, int index) {\n    // Move all elements at and after index index backward by one position\n    for (int i = size - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.java
/* Insert element num at index index in the array */\nvoid insert(int[] nums, int num, int index) {\n    // Move all elements at and after index index backward by one position\n    for (int i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.cs
/* Insert element num at index index in the array */\nvoid Insert(int[] nums, int num, int index) {\n    // Move all elements at and after index index backward by one position\n    for (int i = nums.Length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.go
/* Insert element num at index index in the array */\nfunc insert(nums []int, num int, index int) {\n    // Move all elements at and after index index backward by one position\n    for i := len(nums) - 1; i > index; i-- {\n        nums[i] = nums[i-1]\n    }\n    // Assign num to the element at index index\n    nums[index] = num\n}\n
array.swift
/* Insert element num at index index in the array */\nfunc insert(nums: inout [Int], num: Int, index: Int) {\n    // Move all elements at and after index index backward by one position\n    for i in nums.indices.dropFirst(index).reversed() {\n        nums[i] = nums[i - 1]\n    }\n    // Assign num to the element at index index\n    nums[index] = num\n}\n
array.js
/* Insert element num at index index in the array */\nfunction insert(nums, num, index) {\n    // Move all elements at and after index index backward by one position\n    for (let i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.ts
/* Insert element num at index index in the array */\nfunction insert(nums: number[], num: number, index: number): void {\n    // Move all elements at and after index index backward by one position\n    for (let i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.dart
/* Insert element _num at array index index */\nvoid insert(List<int> nums, int _num, int index) {\n  // Move all elements at and after index index backward by one position\n  for (var i = nums.length - 1; i > index; i--) {\n    nums[i] = nums[i - 1];\n  }\n  // Assign _num to element at index\n  nums[index] = _num;\n}\n
array.rs
/* Insert element num at index index in the array */\nfn insert(nums: &mut [i32], num: i32, index: usize) {\n    // Move all elements at and after index index backward by one position\n    for i in (index + 1..nums.len()).rev() {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.c
/* Insert element num at index index in the array */\nvoid insert(int *nums, int size, int num, int index) {\n    // Move all elements at and after index index backward by one position\n    for (int i = size - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.kt
/* Insert element num at index index in the array */\nfun insert(nums: IntArray, num: Int, index: Int) {\n    // Move all elements at and after index index backward by one position\n    for (i in nums.size - 1 downTo index + 1) {\n        nums[i] = nums[i - 1]\n    }\n    // Assign num to the element at index index\n    nums[index] = num\n}\n
array.rb
### Insert element num at index in array ###\ndef insert(nums, num, index)\n  # Move all elements at and after index index backward by one position\n  for i in (nums.length - 1).downto(index + 1)\n    nums[i] = nums[i - 1]\n  end\n\n  # Assign num to the element at index index\n  nums[index] = num\nend\n
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#4-removing-elements","level":3,"title":"4.   Removing Elements","text":"

Similarly, as shown in Figure 4-4, to delete the element at index \\(i\\), we need to shift all elements after index \\(i\\) forward by one position.

Figure 4-4   Example of removing an element from an array

Note that after the deletion is complete, the original last element becomes \"meaningless\", so we do not need to specifically modify it.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def remove(nums: list[int], index: int):\n    \"\"\"Remove the element at index index\"\"\"\n    # Move all elements after index index forward by one position\n    for i in range(index, len(nums) - 1):\n        nums[i] = nums[i + 1]\n
array.cpp
/* Remove the element at index index */\nvoid remove(int *nums, int size, int index) {\n    // Move all elements after index index forward by one position\n    for (int i = index; i < size - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.java
/* Remove the element at index index */\nvoid remove(int[] nums, int index) {\n    // Move all elements after index index forward by one position\n    for (int i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.cs
/* Remove the element at index index */\nvoid Remove(int[] nums, int index) {\n    // Move all elements after index index forward by one position\n    for (int i = index; i < nums.Length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.go
/* Remove the element at index index */\nfunc remove(nums []int, index int) {\n    // Move all elements after index index forward by one position\n    for i := index; i < len(nums)-1; i++ {\n        nums[i] = nums[i+1]\n    }\n}\n
array.swift
/* Remove the element at index index */\nfunc remove(nums: inout [Int], index: Int) {\n    // Move all elements after index index forward by one position\n    for i in nums.indices.dropFirst(index).dropLast() {\n        nums[i] = nums[i + 1]\n    }\n}\n
array.js
/* Remove the element at index index */\nfunction remove(nums, index) {\n    // Move all elements after index index forward by one position\n    for (let i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.ts
/* Remove the element at index index */\nfunction remove(nums: number[], index: number): void {\n    // Move all elements after index index forward by one position\n    for (let i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.dart
/* Remove the element at index index */\nvoid remove(List<int> nums, int index) {\n  // Move all elements after index index forward by one position\n  for (var i = index; i < nums.length - 1; i++) {\n    nums[i] = nums[i + 1];\n  }\n}\n
array.rs
/* Remove the element at index index */\nfn remove(nums: &mut [i32], index: usize) {\n    // Move all elements after index index forward by one position\n    for i in index..nums.len() - 1 {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.c
/* Remove the element at index index */\n// Note: stdio.h occupies the remove keyword\nvoid removeItem(int *nums, int size, int index) {\n    // Move all elements after index index forward by one position\n    for (int i = index; i < size - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.kt
/* Remove the element at index index */\nfun remove(nums: IntArray, index: Int) {\n    // Move all elements after index index forward by one position\n    for (i in index..<nums.size - 1) {\n        nums[i] = nums[i + 1]\n    }\n}\n
array.rb
### Delete element at index ###\ndef remove(nums, index)\n  # Move all elements after index index forward by one position\n  for i in index...(nums.length - 1)\n    nums[i] = nums[i + 1]\n  end\nend\n

Overall, array insertion and deletion operations have the following drawbacks:

  • High time complexity: The average time complexity for both insertion and deletion in arrays is \\(O(n)\\), where \\(n\\) is the length of the array.
  • Loss of elements: Since the length of an array is immutable, after inserting an element, elements that exceed the array's length will be lost.
  • Memory waste: We can initialize a relatively long array and only use the front portion, so that when inserting data, the lost elements at the end are \"meaningless\", but this causes some memory space to be wasted.
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#5-traversing-arrays","level":3,"title":"5.   Traversing Arrays","text":"

In most programming languages, we can traverse an array either by index or by directly iterating through each element in the array:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def traverse(nums: list[int]):\n    \"\"\"Traverse array\"\"\"\n    count = 0\n    # Traverse array by index\n    for i in range(len(nums)):\n        count += nums[i]\n    # Direct traversal of array elements\n    for num in nums:\n        count += num\n    # Traverse simultaneously data index and elements\n    for i, num in enumerate(nums):\n        count += nums[i]\n        count += num\n
array.cpp
/* Traverse array */\nvoid traverse(int *nums, int size) {\n    int count = 0;\n    // Traverse array by index\n    for (int i = 0; i < size; i++) {\n        count += nums[i];\n    }\n}\n
array.java
/* Traverse array */\nvoid traverse(int[] nums) {\n    int count = 0;\n    // Traverse array by index\n    for (int i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Direct traversal of array elements\n    for (int num : nums) {\n        count += num;\n    }\n}\n
array.cs
/* Traverse array */\nvoid Traverse(int[] nums) {\n    int count = 0;\n    // Traverse array by index\n    for (int i = 0; i < nums.Length; i++) {\n        count += nums[i];\n    }\n    // Direct traversal of array elements\n    foreach (int num in nums) {\n        count += num;\n    }\n}\n
array.go
/* Traverse array */\nfunc traverse(nums []int) {\n    count := 0\n    // Traverse array by index\n    for i := 0; i < len(nums); i++ {\n        count += nums[i]\n    }\n    count = 0\n    // Direct traversal of array elements\n    for _, num := range nums {\n        count += num\n    }\n    // Traverse simultaneously data index and elements\n    for i, num := range nums {\n        count += nums[i]\n        count += num\n    }\n}\n
array.swift
/* Traverse array */\nfunc traverse(nums: [Int]) {\n    var count = 0\n    // Traverse array by index\n    for i in nums.indices {\n        count += nums[i]\n    }\n    // Direct traversal of array elements\n    for num in nums {\n        count += num\n    }\n    // Traverse simultaneously data index and elements\n    for (i, num) in nums.enumerated() {\n        count += nums[i]\n        count += num\n    }\n}\n
array.js
/* Traverse array */\nfunction traverse(nums) {\n    let count = 0;\n    // Traverse array by index\n    for (let i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Direct traversal of array elements\n    for (const num of nums) {\n        count += num;\n    }\n}\n
array.ts
/* Traverse array */\nfunction traverse(nums: number[]): void {\n    let count = 0;\n    // Traverse array by index\n    for (let i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Direct traversal of array elements\n    for (const num of nums) {\n        count += num;\n    }\n}\n
array.dart
/* Traverse array elements */\nvoid traverse(List<int> nums) {\n  int count = 0;\n  // Traverse array by index\n  for (var i = 0; i < nums.length; i++) {\n    count += nums[i];\n  }\n  // Direct traversal of array elements\n  for (int _num in nums) {\n    count += _num;\n  }\n  // Traverse array using forEach method\n  nums.forEach((_num) {\n    count += _num;\n  });\n}\n
array.rs
/* Traverse array */\nfn traverse(nums: &[i32]) {\n    let mut _count = 0;\n    // Traverse array by index\n    for i in 0..nums.len() {\n        _count += nums[i];\n    }\n    // Direct traversal of array elements\n    _count = 0;\n    for &num in nums {\n        _count += num;\n    }\n}\n
array.c
/* Traverse array */\nvoid traverse(int *nums, int size) {\n    int count = 0;\n    // Traverse array by index\n    for (int i = 0; i < size; i++) {\n        count += nums[i];\n    }\n}\n
array.kt
/* Traverse array */\nfun traverse(nums: IntArray) {\n    var count = 0\n    // Traverse array by index\n    for (i in nums.indices) {\n        count += nums[i]\n    }\n    // Direct traversal of array elements\n    for (j in nums) {\n        count += j\n    }\n}\n
array.rb
### Traverse array ###\ndef traverse(nums)\n  count = 0\n\n  # Traverse array by index\n  for i in 0...nums.length\n    count += nums[i]\n  end\n\n  # Direct traversal of array elements\n  for num in nums\n    count += num\n  end\nend\n
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#6-finding-elements","level":3,"title":"6.   Finding Elements","text":"

Finding a specified element in an array requires traversing the array and checking whether the element value matches in each iteration; if it matches, output the corresponding index.

Since an array is a linear data structure, the above search operation is called a \"linear search\".

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def find(nums: list[int], target: int) -> int:\n    \"\"\"Find the specified element in the array\"\"\"\n    for i in range(len(nums)):\n        if nums[i] == target:\n            return i\n    return -1\n
array.cpp
/* Find the specified element in the array */\nint find(int *nums, int size, int target) {\n    for (int i = 0; i < size; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
array.java
/* Find the specified element in the array */\nint find(int[] nums, int target) {\n    for (int i = 0; i < nums.length; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
array.cs
/* Find the specified element in the array */\nint Find(int[] nums, int target) {\n    for (int i = 0; i < nums.Length; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
array.go
/* Find the specified element in the array */\nfunc find(nums []int, target int) (index int) {\n    index = -1\n    for i := 0; i < len(nums); i++ {\n        if nums[i] == target {\n            index = i\n            break\n        }\n    }\n    return\n}\n
array.swift
/* Find the specified element in the array */\nfunc find(nums: [Int], target: Int) -> Int {\n    for i in nums.indices {\n        if nums[i] == target {\n            return i\n        }\n    }\n    return -1\n}\n
array.js
/* Find the specified element in the array */\nfunction find(nums, target) {\n    for (let i = 0; i < nums.length; i++) {\n        if (nums[i] === target) return i;\n    }\n    return -1;\n}\n
array.ts
/* Find the specified element in the array */\nfunction find(nums: number[], target: number): number {\n    for (let i = 0; i < nums.length; i++) {\n        if (nums[i] === target) {\n            return i;\n        }\n    }\n    return -1;\n}\n
array.dart
/* Find the specified element in the array */\nint find(List<int> nums, int target) {\n  for (var i = 0; i < nums.length; i++) {\n    if (nums[i] == target) return i;\n  }\n  return -1;\n}\n
array.rs
/* Find the specified element in the array */\nfn find(nums: &[i32], target: i32) -> Option<usize> {\n    for i in 0..nums.len() {\n        if nums[i] == target {\n            return Some(i);\n        }\n    }\n    None\n}\n
array.c
/* Find the specified element in the array */\nint find(int *nums, int size, int target) {\n    for (int i = 0; i < size; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
array.kt
/* Find the specified element in the array */\nfun find(nums: IntArray, target: Int): Int {\n    for (i in nums.indices) {\n        if (nums[i] == target)\n            return i\n    }\n    return -1\n}\n
array.rb
### Find specified element in array ###\ndef find(nums, target)\n  for i in 0...nums.length\n    return i if nums[i] == target\n  end\n\n  -1\nend\n
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#7-expanding-arrays","level":3,"title":"7.   Expanding Arrays","text":"

In complex system environments, programs cannot guarantee that the memory space after an array is available, making it unsafe to expand the array's capacity. Therefore, in most programming languages, the length of an array is immutable.

If we want to expand an array, we need to create a new, larger array and then copy the original array elements to the new array one by one. This is an \\(O(n)\\) operation, which is very time-consuming when the array is large. The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def extend(nums: list[int], enlarge: int) -> list[int]:\n    \"\"\"Extend array length\"\"\"\n    # Initialize an array with extended length\n    res = [0] * (len(nums) + enlarge)\n    # Copy all elements from the original array to the new array\n    for i in range(len(nums)):\n        res[i] = nums[i]\n    # Return the extended new array\n    return res\n
array.cpp
/* Extend array length */\nint *extend(int *nums, int size, int enlarge) {\n    // Initialize an array with extended length\n    int *res = new int[size + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < size; i++) {\n        res[i] = nums[i];\n    }\n    // Free memory\n    delete[] nums;\n    // Return the extended new array\n    return res;\n}\n
array.java
/* Extend array length */\nint[] extend(int[] nums, int enlarge) {\n    // Initialize an array with extended length\n    int[] res = new int[nums.length + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the extended new array\n    return res;\n}\n
array.cs
/* Extend array length */\nint[] Extend(int[] nums, int enlarge) {\n    // Initialize an array with extended length\n    int[] res = new int[nums.Length + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < nums.Length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the extended new array\n    return res;\n}\n
array.go
/* Extend array length */\nfunc extend(nums []int, enlarge int) []int {\n    // Initialize an array with extended length\n    res := make([]int, len(nums)+enlarge)\n    // Copy all elements from the original array to the new array\n    for i, num := range nums {\n        res[i] = num\n    }\n    // Return the extended new array\n    return res\n}\n
array.swift
/* Extend array length */\nfunc extend(nums: [Int], enlarge: Int) -> [Int] {\n    // Initialize an array with extended length\n    var res = Array(repeating: 0, count: nums.count + enlarge)\n    // Copy all elements from the original array to the new array\n    for i in nums.indices {\n        res[i] = nums[i]\n    }\n    // Return the extended new array\n    return res\n}\n
array.js
/* Extend array length */\n// Note: JavaScript's Array is dynamic array, can be directly expanded\n// For learning purposes, this function treats Array as fixed-length array\nfunction extend(nums, enlarge) {\n    // Initialize an array with extended length\n    const res = new Array(nums.length + enlarge).fill(0);\n    // Copy all elements from the original array to the new array\n    for (let i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the extended new array\n    return res;\n}\n
array.ts
/* Extend array length */\n// Note: TypeScript's Array is dynamic array, can be directly expanded\n// For learning purposes, this function treats Array as fixed-length array\nfunction extend(nums: number[], enlarge: number): number[] {\n    // Initialize an array with extended length\n    const res = new Array(nums.length + enlarge).fill(0);\n    // Copy all elements from the original array to the new array\n    for (let i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the extended new array\n    return res;\n}\n
array.dart
/* Extend array length */\nList<int> extend(List<int> nums, int enlarge) {\n  // Initialize an array with extended length\n  List<int> res = List.filled(nums.length + enlarge, 0);\n  // Copy all elements from the original array to the new array\n  for (var i = 0; i < nums.length; i++) {\n    res[i] = nums[i];\n  }\n  // Return the extended new array\n  return res;\n}\n
array.rs
/* Extend array length */\nfn extend(nums: &[i32], enlarge: usize) -> Vec<i32> {\n    // Initialize an array with extended length\n    let mut res: Vec<i32> = vec![0; nums.len() + enlarge];\n    // Copy all elements from original array to new\n    res[0..nums.len()].copy_from_slice(nums);\n\n    // Return the extended new array\n    res\n}\n
array.c
/* Extend array length */\nint *extend(int *nums, int size, int enlarge) {\n    // Initialize an array with extended length\n    int *res = (int *)malloc(sizeof(int) * (size + enlarge));\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < size; i++) {\n        res[i] = nums[i];\n    }\n    // Initialize expanded space\n    for (int i = size; i < size + enlarge; i++) {\n        res[i] = 0;\n    }\n    // Return the extended new array\n    return res;\n}\n
array.kt
/* Extend array length */\nfun extend(nums: IntArray, enlarge: Int): IntArray {\n    // Initialize an array with extended length\n    val res = IntArray(nums.size + enlarge)\n    // Copy all elements from the original array to the new array\n    for (i in nums.indices) {\n        res[i] = nums[i]\n    }\n    // Return the extended new array\n    return res\n}\n
array.rb
### Extend array length ###\n# Note: Ruby's Array is dynamic array, can be directly expanded\n# For learning purposes, this function treats Array as fixed-length array\ndef extend(nums, enlarge)\n  # Initialize an array with extended length\n  res = Array.new(nums.length + enlarge, 0)\n\n  # Copy all elements from the original array to the new array\n  for i in 0...nums.length\n    res[i] = nums[i]\n  end\n\n  # Return the extended new array\n  res\nend\n
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#412-advantages-and-limitations-of-arrays","level":2,"title":"4.1.2   Advantages and Limitations of Arrays","text":"

Arrays are stored in contiguous memory space with elements of the same type. This approach contains rich prior information that the system can use to optimize the efficiency of data structure operations.

  • High space efficiency: Arrays allocate contiguous memory blocks for data without additional structural overhead.
  • Support for random access: Arrays allow accessing any element in \\(O(1)\\) time.
  • Cache locality: When accessing array elements, the computer not only loads the element but also caches the surrounding data, thereby leveraging the cache to improve the execution speed of subsequent operations.

Contiguous space storage is a double-edged sword with the following limitations:

  • Low insertion and deletion efficiency: When an array has many elements, insertion and deletion operations require shifting a large number of elements.
  • Immutable length: After an array is initialized, its length is fixed. Expanding the array requires copying all data to a new array, which is very costly.
  • Space waste: If the allocated size of an array exceeds what is actually needed, the extra space is wasted.
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#413-typical-applications-of-arrays","level":2,"title":"4.1.3   Typical Applications of Arrays","text":"

Arrays are a fundamental and common data structure, frequently used in various algorithms and for implementing various complex data structures.

  • Random access: If we want to randomly sample some items, we can use an array to store them and generate a random sequence to implement random sampling based on indices.
  • Sorting and searching: Arrays are the most commonly used data structure for sorting and searching algorithms. Quick sort, merge sort, binary search, and others are primarily performed on arrays.
  • Lookup tables: When we need to quickly find an element or its corresponding relationship, we can use an array as a lookup table. For example, if we want to implement a mapping from characters to ASCII codes, we can use the ASCII code value of a character as an index, with the corresponding element stored at that position in the array.
  • Machine learning: Neural networks make extensive use of linear algebra operations between vectors, matrices, and tensors, all of which are constructed in the form of arrays. Arrays are the most commonly used data structure in neural network programming.
  • Data structure implementation: Arrays can be used to implement stacks, queues, hash tables, heaps, graphs, and other data structures. For example, the adjacency matrix representation of a graph is essentially a two-dimensional array.
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/","level":1,"title":"4.2   Linked List","text":"

Memory space is a shared resource for all programs. In a complex system runtime environment, available memory space may be scattered throughout the memory. We know that the memory space for storing an array must be contiguous, and when the array is very large, the memory may not be able to provide such a large contiguous space. This is where the flexibility advantage of linked lists becomes apparent.

A linked list is a linear data structure in which each element is a node object, and the nodes are connected through \"references\". A reference records the memory address of the next node, through which the next node can be accessed from the current node.

The design of linked lists allows nodes to be stored scattered throughout the memory, and their memory addresses do not need to be contiguous.

Figure 4-5   Linked list definition and storage method

Observing Figure 4-5, the basic unit of a linked list is a node object. Each node contains two pieces of data: the node's \"value\" and a \"reference\" to the next node.

  • The first node of a linked list is called the \"head node\", and the last node is called the \"tail node\".
  • The tail node points to \"null\", which is denoted as null, nullptr, and None in Java, C++, and Python, respectively.
  • In languages that support pointers, such as C, C++, Go, and Rust, the aforementioned \"reference\" should be replaced with \"pointer\".

As shown in the following code, a linked list node ListNode contains not only a value but also an additional reference (pointer). Therefore, linked lists occupy more memory space than arrays when storing the same amount of data.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
class ListNode:\n    \"\"\"Linked list node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val               # Node value\n        self.next: ListNode | None = None # Reference to the next node\n
/* Linked list node structure */\nstruct ListNode {\n    int val;         // Node value\n    ListNode *next;  // Pointer to the next node\n    ListNode(int x) : val(x), next(nullptr) {}  // Constructor\n};\n
/* Linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode next;  // Reference to the next node\n    ListNode(int x) { val = x; }  // Constructor\n}\n
/* Linked list node class */\nclass ListNode(int x) {  // Constructor\n    int val = x;         // Node value\n    ListNode? next;      // Reference to the next node\n}\n
/* Linked list node structure */\ntype ListNode struct {\n    Val  int       // Node value\n    Next *ListNode // Pointer to the next node\n}\n\n// NewListNode Constructor, creates a new linked list\nfunc NewListNode(val int) *ListNode {\n    return &ListNode{\n        Val:  val,\n        Next: nil,\n    }\n}\n
/* Linked list node class */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Reference to the next node\n\n    init(x: Int) { // Constructor\n        val = x\n    }\n}\n
/* Linked list node class */\nclass ListNode {\n    constructor(val, next) {\n        this.val = (val === undefined ? 0 : val);       // Node value\n        this.next = (next === undefined ? null : next); // Reference to the next node\n    }\n}\n
/* Linked list node class */\nclass ListNode {\n    val: number;\n    next: ListNode | null;\n    constructor(val?: number, next?: ListNode | null) {\n        this.val = val === undefined ? 0 : val;        // Node value\n        this.next = next === undefined ? null : next;  // Reference to the next node\n    }\n}\n
/* Linked list node class */\nclass ListNode {\n  int val; // Node value\n  ListNode? next; // Reference to the next node\n  ListNode(this.val, [this.next]); // Constructor\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n/* Linked list node class */\n#[derive(Debug)]\nstruct ListNode {\n    val: i32, // Node value\n    next: Option<Rc<RefCell<ListNode>>>, // Pointer to the next node\n}\n
/* Linked list node structure */\ntypedef struct ListNode {\n    int val;               // Node value\n    struct ListNode *next; // Pointer to the next node\n} ListNode;\n\n/* Constructor */\nListNode *newListNode(int val) {\n    ListNode *node;\n    node = (ListNode *) malloc(sizeof(ListNode));\n    node->val = val;\n    node->next = NULL;\n    return node;\n}\n
/* Linked list node class */\n// Constructor\nclass ListNode(x: Int) {\n    val _val: Int = x          // Node value\n    val next: ListNode? = null // Reference to the next node\n}\n
# Linked list node class\nclass ListNode\n  attr_accessor :val  # Node value\n  attr_accessor :next # Reference to the next node\n\n  def initialize(val=0, next_node=nil)\n    @val = val\n    @next = next_node\n  end\nend\n
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#421-common-linked-list-operations","level":2,"title":"4.2.1   Common Linked List Operations","text":"","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#1-initializing-a-linked-list","level":3,"title":"1.   Initializing a Linked List","text":"

Building a linked list involves two steps: first, initializing each node object; second, constructing the reference relationships between nodes. Once initialization is complete, we can traverse all nodes starting from the head node of the linked list through the reference next.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
# Initialize linked list 1 -> 3 -> 2 -> 5 -> 4\n# Initialize each node\nn0 = ListNode(1)\nn1 = ListNode(3)\nn2 = ListNode(2)\nn3 = ListNode(5)\nn4 = ListNode(4)\n# Build references between nodes\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
linked_list.cpp
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nListNode* n0 = new ListNode(1);\nListNode* n1 = new ListNode(3);\nListNode* n2 = new ListNode(2);\nListNode* n3 = new ListNode(5);\nListNode* n4 = new ListNode(4);\n// Build references between nodes\nn0->next = n1;\nn1->next = n2;\nn2->next = n3;\nn3->next = n4;\n
linked_list.java
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nListNode n0 = new ListNode(1);\nListNode n1 = new ListNode(3);\nListNode n2 = new ListNode(2);\nListNode n3 = new ListNode(5);\nListNode n4 = new ListNode(4);\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.cs
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nListNode n0 = new(1);\nListNode n1 = new(3);\nListNode n2 = new(2);\nListNode n3 = new(5);\nListNode n4 = new(4);\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.go
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nn0 := NewListNode(1)\nn1 := NewListNode(3)\nn2 := NewListNode(2)\nn3 := NewListNode(5)\nn4 := NewListNode(4)\n// Build references between nodes\nn0.Next = n1\nn1.Next = n2\nn2.Next = n3\nn3.Next = n4\n
linked_list.swift
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nlet n0 = ListNode(x: 1)\nlet n1 = ListNode(x: 3)\nlet n2 = ListNode(x: 2)\nlet n3 = ListNode(x: 5)\nlet n4 = ListNode(x: 4)\n// Build references between nodes\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
linked_list.js
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.ts
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.dart
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\\\n// Initialize each node\nListNode n0 = ListNode(1);\nListNode n1 = ListNode(3);\nListNode n2 = ListNode(2);\nListNode n3 = ListNode(5);\nListNode n4 = ListNode(4);\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.rs
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nlet n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None }));\nlet n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None }));\nlet n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None }));\nlet n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None }));\nlet n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None }));\n\n// Build references between nodes\nn0.borrow_mut().next = Some(n1.clone());\nn1.borrow_mut().next = Some(n2.clone());\nn2.borrow_mut().next = Some(n3.clone());\nn3.borrow_mut().next = Some(n4.clone());\n
linked_list.c
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nListNode* n0 = newListNode(1);\nListNode* n1 = newListNode(3);\nListNode* n2 = newListNode(2);\nListNode* n3 = newListNode(5);\nListNode* n4 = newListNode(4);\n// Build references between nodes\nn0->next = n1;\nn1->next = n2;\nn2->next = n3;\nn3->next = n4;\n
linked_list.kt
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nval n0 = ListNode(1)\nval n1 = ListNode(3)\nval n2 = ListNode(2)\nval n3 = ListNode(5)\nval n4 = ListNode(4)\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.rb
# Initialize linked list 1 -> 3 -> 2 -> 5 -> 4\n# Initialize each node\nn0 = ListNode.new(1)\nn1 = ListNode.new(3)\nn2 = ListNode.new(2)\nn3 = ListNode.new(5)\nn4 = ListNode.new(4)\n# Build references between nodes\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
Code Visualization

Full Screen >

An array is a single variable; for example, an array nums contains elements nums[0], nums[1], etc. A linked list, however, is composed of multiple independent node objects. We typically use the head node as the reference to the linked list; for example, the linked list in the above code can be referred to as linked list n0.

","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#2-inserting-a-node","level":3,"title":"2.   Inserting a Node","text":"

Inserting a node in a linked list is very easy. As shown in Figure 4-6, suppose we want to insert a new node P between two adjacent nodes n0 and n1. We only need to change two node references (pointers), with a time complexity of \\(O(1)\\).

In contrast, the time complexity of inserting an element in an array is \\(O(n)\\), which is inefficient when dealing with large amounts of data.

Figure 4-6   Example of inserting a node into a linked list

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
def insert(n0: ListNode, P: ListNode):\n    \"\"\"Insert node P after node n0 in the linked list\"\"\"\n    n1 = n0.next\n    P.next = n1\n    n0.next = P\n
linked_list.cpp
/* Insert node P after node n0 in the linked list */\nvoid insert(ListNode *n0, ListNode *P) {\n    ListNode *n1 = n0->next;\n    P->next = n1;\n    n0->next = P;\n}\n
linked_list.java
/* Insert node P after node n0 in the linked list */\nvoid insert(ListNode n0, ListNode P) {\n    ListNode n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
linked_list.cs
/* Insert node P after node n0 in the linked list */\nvoid Insert(ListNode n0, ListNode P) {\n    ListNode? n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
linked_list.go
/* Insert node P after node n0 in the linked list */\nfunc insertNode(n0 *ListNode, P *ListNode) {\n    n1 := n0.Next\n    P.Next = n1\n    n0.Next = P\n}\n
linked_list.swift
/* Insert node P after node n0 in the linked list */\nfunc insert(n0: ListNode, P: ListNode) {\n    let n1 = n0.next\n    P.next = n1\n    n0.next = P\n}\n
linked_list.js
/* Insert node P after node n0 in the linked list */\nfunction insert(n0, P) {\n    const n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
linked_list.ts
/* Insert node P after node n0 in the linked list */\nfunction insert(n0: ListNode, P: ListNode): void {\n    const n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
linked_list.dart
/* Insert node P after node n0 in the linked list */\nvoid insert(ListNode n0, ListNode P) {\n  ListNode? n1 = n0.next;\n  P.next = n1;\n  n0.next = P;\n}\n
linked_list.rs
/* Insert node P after node n0 in the linked list */\n#[allow(non_snake_case)]\npub fn insert<T>(n0: &Rc<RefCell<ListNode<T>>>, P: Rc<RefCell<ListNode<T>>>) {\n    let n1 = n0.borrow_mut().next.take();\n    P.borrow_mut().next = n1;\n    n0.borrow_mut().next = Some(P);\n}\n
linked_list.c
/* Insert node P after node n0 in the linked list */\nvoid insert(ListNode *n0, ListNode *P) {\n    ListNode *n1 = n0->next;\n    P->next = n1;\n    n0->next = P;\n}\n
linked_list.kt
/* Insert node P after node n0 in the linked list */\nfun insert(n0: ListNode?, p: ListNode?) {\n    val n1 = n0?.next\n    p?.next = n1\n    n0?.next = p\n}\n
linked_list.rb
### Insert node _p after node n0 in linked list ###\n# Ruby's `p` is a built-in function, `P` is a constant, so use `_p` instead\ndef insert(n0, _p)\n  n1 = n0.next\n  _p.next = n1\n  n0.next = _p\nend\n
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#3-removing-a-node","level":3,"title":"3.   Removing a Node","text":"

As shown in Figure 4-7, removing a node in a linked list is also very convenient. We only need to change one node's reference (pointer).

Note that although node P still points to n1 after the deletion operation is complete, the linked list can no longer access P when traversing, which means P no longer belongs to this linked list.

Figure 4-7   Removing a node from a linked list

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
def remove(n0: ListNode):\n    \"\"\"Remove the first node after node n0 in the linked list\"\"\"\n    if not n0.next:\n        return\n    # n0 -> P -> n1\n    P = n0.next\n    n1 = P.next\n    n0.next = n1\n
linked_list.cpp
/* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode *n0) {\n    if (n0->next == nullptr)\n        return;\n    // n0 -> P -> n1\n    ListNode *P = n0->next;\n    ListNode *n1 = P->next;\n    n0->next = n1;\n    // Free memory\n    delete P;\n}\n
linked_list.java
/* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode n0) {\n    if (n0.next == null)\n        return;\n    // n0 -> P -> n1\n    ListNode P = n0.next;\n    ListNode n1 = P.next;\n    n0.next = n1;\n}\n
linked_list.cs
/* Remove the first node after node n0 in the linked list */\nvoid Remove(ListNode n0) {\n    if (n0.next == null)\n        return;\n    // n0 -> P -> n1\n    ListNode P = n0.next;\n    ListNode? n1 = P.next;\n    n0.next = n1;\n}\n
linked_list.go
/* Remove the first node after node n0 in the linked list */\nfunc removeItem(n0 *ListNode) {\n    if n0.Next == nil {\n        return\n    }\n    // n0 -> P -> n1\n    P := n0.Next\n    n1 := P.Next\n    n0.Next = n1\n}\n
linked_list.swift
/* Remove the first node after node n0 in the linked list */\nfunc remove(n0: ListNode) {\n    if n0.next == nil {\n        return\n    }\n    // n0 -> P -> n1\n    let P = n0.next\n    let n1 = P?.next\n    n0.next = n1\n}\n
linked_list.js
/* Remove the first node after node n0 in the linked list */\nfunction remove(n0) {\n    if (!n0.next) return;\n    // n0 -> P -> n1\n    const P = n0.next;\n    const n1 = P.next;\n    n0.next = n1;\n}\n
linked_list.ts
/* Remove the first node after node n0 in the linked list */\nfunction remove(n0: ListNode): void {\n    if (!n0.next) {\n        return;\n    }\n    // n0 -> P -> n1\n    const P = n0.next;\n    const n1 = P.next;\n    n0.next = n1;\n}\n
linked_list.dart
/* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode n0) {\n  if (n0.next == null) return;\n  // n0 -> P -> n1\n  ListNode P = n0.next!;\n  ListNode? n1 = P.next;\n  n0.next = n1;\n}\n
linked_list.rs
/* Remove the first node after node n0 in the linked list */\n#[allow(non_snake_case)]\npub fn remove<T>(n0: &Rc<RefCell<ListNode<T>>>) {\n    // n0 -> P -> n1\n    let P = n0.borrow_mut().next.take();\n    if let Some(node) = P {\n        let n1 = node.borrow_mut().next.take();\n        n0.borrow_mut().next = n1;\n    }\n}\n
linked_list.c
/* Remove the first node after node n0 in the linked list */\n// Note: stdio.h occupies the remove keyword\nvoid removeItem(ListNode *n0) {\n    if (!n0->next)\n        return;\n    // n0 -> P -> n1\n    ListNode *P = n0->next;\n    ListNode *n1 = P->next;\n    n0->next = n1;\n    // Free memory\n    free(P);\n}\n
linked_list.kt
/* Remove the first node after node n0 in the linked list */\nfun remove(n0: ListNode?) {\n    if (n0?.next == null)\n        return\n    // n0 -> P -> n1\n    val p = n0.next\n    val n1 = p?.next\n    n0.next = n1\n}\n
linked_list.rb
### Delete first node after node n0 in linked list ###\ndef remove(n0)\n  return if n0.next.nil?\n\n  # n0 -> remove_node -> n1\n  remove_node = n0.next\n  n1 = remove_node.next\n  n0.next = n1\nend\n
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#4-accessing-a-node","level":3,"title":"4.   Accessing a Node","text":"

Accessing nodes in a linked list is less efficient. As mentioned in the previous section, we can access any element in an array in \\(O(1)\\) time. This is not the case with linked lists. The program needs to start from the head node and traverse backward one by one until the target node is found. That is, accessing the \\(i\\)-th node in a linked list requires \\(i - 1\\) iterations, with a time complexity of \\(O(n)\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
def access(head: ListNode, index: int) -> ListNode | None:\n    \"\"\"Access the node at index index in the linked list\"\"\"\n    for _ in range(index):\n        if not head:\n            return None\n        head = head.next\n    return head\n
linked_list.cpp
/* Access the node at index index in the linked list */\nListNode *access(ListNode *head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == nullptr)\n            return nullptr;\n        head = head->next;\n    }\n    return head;\n}\n
linked_list.java
/* Access the node at index index in the linked list */\nListNode access(ListNode head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == null)\n            return null;\n        head = head.next;\n    }\n    return head;\n}\n
linked_list.cs
/* Access the node at index index in the linked list */\nListNode? Access(ListNode? head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == null)\n            return null;\n        head = head.next;\n    }\n    return head;\n}\n
linked_list.go
/* Access the node at index index in the linked list */\nfunc access(head *ListNode, index int) *ListNode {\n    for i := 0; i < index; i++ {\n        if head == nil {\n            return nil\n        }\n        head = head.Next\n    }\n    return head\n}\n
linked_list.swift
/* Access the node at index index in the linked list */\nfunc access(head: ListNode, index: Int) -> ListNode? {\n    var head: ListNode? = head\n    for _ in 0 ..< index {\n        if head == nil {\n            return nil\n        }\n        head = head?.next\n    }\n    return head\n}\n
linked_list.js
/* Access the node at index index in the linked list */\nfunction access(head, index) {\n    for (let i = 0; i < index; i++) {\n        if (!head) {\n            return null;\n        }\n        head = head.next;\n    }\n    return head;\n}\n
linked_list.ts
/* Access the node at index index in the linked list */\nfunction access(head: ListNode | null, index: number): ListNode | null {\n    for (let i = 0; i < index; i++) {\n        if (!head) {\n            return null;\n        }\n        head = head.next;\n    }\n    return head;\n}\n
linked_list.dart
/* Access the node at index index in the linked list */\nListNode? access(ListNode? head, int index) {\n  for (var i = 0; i < index; i++) {\n    if (head == null) return null;\n    head = head.next;\n  }\n  return head;\n}\n
linked_list.rs
/* Access the node at index index in the linked list */\npub fn access<T>(head: Rc<RefCell<ListNode<T>>>, index: i32) -> Option<Rc<RefCell<ListNode<T>>>> {\n    fn dfs<T>(\n        head: Option<&Rc<RefCell<ListNode<T>>>>,\n        index: i32,\n    ) -> Option<Rc<RefCell<ListNode<T>>>> {\n        if index <= 0 {\n            return head.cloned();\n        }\n\n        if let Some(node) = head {\n            dfs(node.borrow().next.as_ref(), index - 1)\n        } else {\n            None\n        }\n    }\n\n    dfs(Some(head).as_ref(), index)\n}\n
linked_list.c
/* Access the node at index index in the linked list */\nListNode *access(ListNode *head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == NULL)\n            return NULL;\n        head = head->next;\n    }\n    return head;\n}\n
linked_list.kt
/* Access the node at index index in the linked list */\nfun access(head: ListNode?, index: Int): ListNode? {\n    var h = head\n    for (i in 0..<index) {\n        if (h == null)\n            return null\n        h = h.next\n    }\n    return h\n}\n
linked_list.rb
### Access node at index in linked list ###\ndef access(head, index)\n  for i in 0...index\n    return nil if head.nil?\n    head = head.next\n  end\n\n  head\nend\n
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#5-finding-a-node","level":3,"title":"5.   Finding a Node","text":"

Traverse the linked list to find a node with value target, and output the index of that node in the linked list. This process is also a linear search. The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
def find(head: ListNode, target: int) -> int:\n    \"\"\"Find the first node with value target in the linked list\"\"\"\n    index = 0\n    while head:\n        if head.val == target:\n            return index\n        head = head.next\n        index += 1\n    return -1\n
linked_list.cpp
/* Find the first node with value target in the linked list */\nint find(ListNode *head, int target) {\n    int index = 0;\n    while (head != nullptr) {\n        if (head->val == target)\n            return index;\n        head = head->next;\n        index++;\n    }\n    return -1;\n}\n
linked_list.java
/* Find the first node with value target in the linked list */\nint find(ListNode head, int target) {\n    int index = 0;\n    while (head != null) {\n        if (head.val == target)\n            return index;\n        head = head.next;\n        index++;\n    }\n    return -1;\n}\n
linked_list.cs
/* Find the first node with value target in the linked list */\nint Find(ListNode? head, int target) {\n    int index = 0;\n    while (head != null) {\n        if (head.val == target)\n            return index;\n        head = head.next;\n        index++;\n    }\n    return -1;\n}\n
linked_list.go
/* Find the first node with value target in the linked list */\nfunc findNode(head *ListNode, target int) int {\n    index := 0\n    for head != nil {\n        if head.Val == target {\n            return index\n        }\n        head = head.Next\n        index++\n    }\n    return -1\n}\n
linked_list.swift
/* Find the first node with value target in the linked list */\nfunc find(head: ListNode, target: Int) -> Int {\n    var head: ListNode? = head\n    var index = 0\n    while head != nil {\n        if head?.val == target {\n            return index\n        }\n        head = head?.next\n        index += 1\n    }\n    return -1\n}\n
linked_list.js
/* Find the first node with value target in the linked list */\nfunction find(head, target) {\n    let index = 0;\n    while (head !== null) {\n        if (head.val === target) {\n            return index;\n        }\n        head = head.next;\n        index += 1;\n    }\n    return -1;\n}\n
linked_list.ts
/* Find the first node with value target in the linked list */\nfunction find(head: ListNode | null, target: number): number {\n    let index = 0;\n    while (head !== null) {\n        if (head.val === target) {\n            return index;\n        }\n        head = head.next;\n        index += 1;\n    }\n    return -1;\n}\n
linked_list.dart
/* Find the first node with value target in the linked list */\nint find(ListNode? head, int target) {\n  int index = 0;\n  while (head != null) {\n    if (head.val == target) {\n      return index;\n    }\n    head = head.next;\n    index++;\n  }\n  return -1;\n}\n
linked_list.rs
/* Find the first node with value target in the linked list */\npub fn find<T: PartialEq>(head: Rc<RefCell<ListNode<T>>>, target: T) -> i32 {\n    fn find<T: PartialEq>(head: Option<&Rc<RefCell<ListNode<T>>>>, target: T, idx: i32) -> i32 {\n        if let Some(node) = head {\n            if node.borrow().val == target {\n                return idx;\n            }\n            return find(node.borrow().next.as_ref(), target, idx + 1);\n        } else {\n            -1\n        }\n    }\n\n    find(Some(head).as_ref(), target, 0)\n}\n
linked_list.c
/* Find the first node with value target in the linked list */\nint find(ListNode *head, int target) {\n    int index = 0;\n    while (head) {\n        if (head->val == target)\n            return index;\n        head = head->next;\n        index++;\n    }\n    return -1;\n}\n
linked_list.kt
/* Find the first node with value target in the linked list */\nfun find(head: ListNode?, target: Int): Int {\n    var index = 0\n    var h = head\n    while (h != null) {\n        if (h._val == target)\n            return index\n        h = h.next\n        index++\n    }\n    return -1\n}\n
linked_list.rb
### Find first node with value target in linked list ###\ndef find(head, target)\n  index = 0\n  while head\n    return index if head.val == target\n    head = head.next\n    index += 1\n  end\n\n  -1\nend\n
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#422-arrays-vs-linked-lists","level":2,"title":"4.2.2   Arrays vs. Linked Lists","text":"

Table 4-1 summarizes the characteristics of arrays and linked lists and compares their operational efficiencies. Since they employ two opposite storage strategies, their various properties and operational efficiencies also exhibit contrasting characteristics.

Table 4-1   Comparison of array and linked list efficiencies

Array Linked List Storage method Contiguous memory space Scattered memory space Capacity expansion Immutable length Flexible expansion Memory efficiency Elements occupy less memory, but space may be wasted Elements occupy more memory Accessing an element \\(O(1)\\) \\(O(n)\\) Adding an element \\(O(n)\\) \\(O(1)\\) Removing an element \\(O(n)\\) \\(O(1)\\)","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#423-common-types-of-linked-lists","level":2,"title":"4.2.3   Common Types of Linked Lists","text":"

As shown in Figure 4-8, there are three common types of linked lists:

  • Singly linked list: This is the ordinary linked list introduced earlier. The nodes of a singly linked list contain a value and a reference to the next node. We call the first node the head node and the last node the tail node, which points to null None.
  • Circular linked list: If we make the tail node of a singly linked list point to the head node (connecting the tail to the head), we get a circular linked list. In a circular linked list, any node can be viewed as the head node.
  • Doubly linked list: Compared to a singly linked list, a doubly linked list records references in both directions. The node definition of a doubly linked list includes references to both the successor node (next node) and the predecessor node (previous node). Compared to a singly linked list, a doubly linked list is more flexible and can traverse the linked list in both directions, but it also requires more memory space.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
class ListNode:\n    \"\"\"Doubly linked list node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                # Node value\n        self.next: ListNode | None = None  # Reference to the successor node\n        self.prev: ListNode | None = None  # Reference to the predecessor node\n
/* Doubly linked list node structure */\nstruct ListNode {\n    int val;         // Node value\n    ListNode *next;  // Pointer to the successor node\n    ListNode *prev;  // Pointer to the predecessor node\n    ListNode(int x) : val(x), next(nullptr), prev(nullptr) {}  // Constructor\n};\n
/* Doubly linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode next;  // Reference to the successor node\n    ListNode prev;  // Reference to the predecessor node\n    ListNode(int x) { val = x; }  // Constructor\n}\n
/* Doubly linked list node class */\nclass ListNode(int x) {  // Constructor\n    int val = x;    // Node value\n    ListNode next;  // Reference to the successor node\n    ListNode prev;  // Reference to the predecessor node\n}\n
/* Doubly linked list node structure */\ntype DoublyListNode struct {\n    Val  int             // Node value\n    Next *DoublyListNode // Pointer to the successor node\n    Prev *DoublyListNode // Pointer to the predecessor node\n}\n\n// NewDoublyListNode Initialization\nfunc NewDoublyListNode(val int) *DoublyListNode {\n    return &DoublyListNode{\n        Val:  val,\n        Next: nil,\n        Prev: nil,\n    }\n}\n
/* Doubly linked list node class */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Reference to the successor node\n    var prev: ListNode? // Reference to the predecessor node\n\n    init(x: Int) { // Constructor\n        val = x\n    }\n}\n
/* Doubly linked list node class */\nclass ListNode {\n    constructor(val, next, prev) {\n        this.val = val  ===  undefined ? 0 : val;        // Node value\n        this.next = next  ===  undefined ? null : next;  // Reference to the successor node\n        this.prev = prev  ===  undefined ? null : prev;  // Reference to the predecessor node\n    }\n}\n
/* Doubly linked list node class */\nclass ListNode {\n    val: number;\n    next: ListNode | null;\n    prev: ListNode | null;\n    constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {\n        this.val = val  ===  undefined ? 0 : val;        // Node value\n        this.next = next  ===  undefined ? null : next;  // Reference to the successor node\n        this.prev = prev  ===  undefined ? null : prev;  // Reference to the predecessor node\n    }\n}\n
/* Doubly linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode? next;  // Reference to the successor node\n    ListNode? prev;  // Reference to the predecessor node\n    ListNode(this.val, [this.next, this.prev]);  // Constructor\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Doubly linked list node type */\n#[derive(Debug)]\nstruct ListNode {\n    val: i32, // Node value\n    next: Option<Rc<RefCell<ListNode>>>, // Pointer to the successor node\n    prev: Option<Rc<RefCell<ListNode>>>, // Pointer to the predecessor node\n}\n\n/* Constructor */\nimpl ListNode {\n    fn new(val: i32) -> Self {\n        ListNode {\n            val,\n            next: None,\n            prev: None,\n        }\n    }\n}\n
/* Doubly linked list node structure */\ntypedef struct ListNode {\n    int val;               // Node value\n    struct ListNode *next; // Pointer to the successor node\n    struct ListNode *prev; // Pointer to the predecessor node\n} ListNode;\n\n/* Constructor */\nListNode *newListNode(int val) {\n    ListNode *node;\n    node = (ListNode *) malloc(sizeof(ListNode));\n    node->val = val;\n    node->next = NULL;\n    node->prev = NULL;\n    return node;\n}\n
/* Doubly linked list node class */\n// Constructor\nclass ListNode(x: Int) {\n    val _val: Int = x           // Node value\n    val next: ListNode? = null  // Reference to the successor node\n    val prev: ListNode? = null  // Reference to the predecessor node\n}\n
# Doubly linked list node class\nclass ListNode\n  attr_accessor :val    # Node value\n  attr_accessor :next   # Reference to the successor node\n  attr_accessor :prev   # Reference to the predecessor node\n\n  def initialize(val=0, next_node=nil, prev_node=nil)\n    @val = val\n    @next = next_node\n    @prev = prev_node\n  end\nend\n

Figure 4-8   Common types of linked lists

","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#424-typical-applications-of-linked-lists","level":2,"title":"4.2.4   Typical Applications of Linked Lists","text":"

Singly linked lists are commonly used to implement stacks, queues, hash tables, and graphs.

  • Stacks and queues: When insertion and deletion operations both occur at one end of the linked list, it exhibits last-in-first-out characteristics, corresponding to a stack. When insertion operations occur at one end of the linked list and deletion operations occur at the other end, it exhibits first-in-first-out characteristics, corresponding to a queue.
  • Hash tables: Separate chaining is one of the mainstream solutions for resolving hash collisions. In this approach, all colliding elements are placed in a linked list.
  • Graphs: An adjacency list is a common way to represent a graph, where each vertex in the graph is associated with a linked list, and each element in the linked list represents another vertex connected to that vertex.

Doubly linked lists are commonly used in scenarios where quick access to the previous and next elements is needed.

  • Advanced data structures: For example, in red-black trees and B-trees, we need to access the parent node of a node, which can be achieved by saving a reference to the parent node in the node, similar to a doubly linked list.
  • Browser history: In web browsers, when a user clicks the forward or backward button, the browser needs to know the previous and next web pages the user visited. The characteristics of doubly linked lists make this operation simple.
  • LRU algorithm: In cache eviction (LRU) algorithms, we need to quickly find the least recently used data and support quick addition and deletion of nodes. Using a doubly linked list is very suitable for this.

Circular linked lists are commonly used in scenarios that require periodic operations, such as operating system resource scheduling.

  • Round-robin scheduling algorithm: In operating systems, round-robin scheduling is a common CPU scheduling algorithm that needs to cycle through a set of processes. Each process is assigned a time slice, and when the time slice expires, the CPU switches to the next process. This cyclic operation can be implemented using a circular linked list.
  • Data buffers: In some data buffer implementations, circular linked lists may also be used. For example, in audio and video players, the data stream may be divided into multiple buffer blocks and placed in a circular linked list to achieve seamless playback.
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/","level":1,"title":"4.3   List","text":"

A list is an abstract data structure concept that represents an ordered collection of elements, supporting operations such as element access, modification, insertion, deletion, and traversal, without requiring users to consider capacity limitations. Lists can be implemented based on linked lists or arrays.

  • A linked list can naturally be viewed as a list, supporting element insertion, deletion, search, and modification operations, and can flexibly expand dynamically.
  • An array also supports element insertion, deletion, search, and modification, but since its length is immutable, it can only be viewed as a list with length limitations.

When implementing lists using arrays, the immutable length property reduces the practicality of the list. This is because we usually cannot determine in advance how much data we need to store, making it difficult to choose an appropriate list length. If the length is too small, it may fail to meet usage requirements; if the length is too large, it will waste memory space.

To solve this problem, we can use a dynamic array to implement a list. It inherits all the advantages of arrays and can dynamically expand during program execution.

In fact, the lists provided in the standard libraries of many programming languages are implemented based on dynamic arrays, such as list in Python, ArrayList in Java, vector in C++, and List in C#. In the following discussion, we will treat \"list\" and \"dynamic array\" as equivalent concepts.

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#431-common-list-operations","level":2,"title":"4.3.1   Common List Operations","text":"","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#1-initialize-a-list","level":3,"title":"1.   Initialize a List","text":"

We typically use two initialization methods: \"without initial values\" and \"with initial values\":

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Initialize a list\n# Without initial values\nnums1: list[int] = []\n# With initial values\nnums: list[int] = [1, 3, 2, 5, 4]\n
list.cpp
/* Initialize a list */\n// Note that vector in C++ is equivalent to nums as described in this article\n// Without initial values\nvector<int> nums1;\n// With initial values\nvector<int> nums = { 1, 3, 2, 5, 4 };\n
list.java
/* Initialize a list */\n// Without initial values\nList<Integer> nums1 = new ArrayList<>();\n// With initial values (note that array elements should use the wrapper class Integer[] instead of int[])\nInteger[] numbers = new Integer[] { 1, 3, 2, 5, 4 };\nList<Integer> nums = new ArrayList<>(Arrays.asList(numbers));\n
list.cs
/* Initialize a list */\n// Without initial values\nList<int> nums1 = [];\n// With initial values\nint[] numbers = [1, 3, 2, 5, 4];\nList<int> nums = [.. numbers];\n
list_test.go
/* Initialize a list */\n// Without initial values\nnums1 := []int{}\n// With initial values\nnums := []int{1, 3, 2, 5, 4}\n
list.swift
/* Initialize a list */\n// Without initial values\nlet nums1: [Int] = []\n// With initial values\nvar nums = [1, 3, 2, 5, 4]\n
list.js
/* Initialize a list */\n// Without initial values\nconst nums1 = [];\n// With initial values\nconst nums = [1, 3, 2, 5, 4];\n
list.ts
/* Initialize a list */\n// Without initial values\nconst nums1: number[] = [];\n// With initial values\nconst nums: number[] = [1, 3, 2, 5, 4];\n
list.dart
/* Initialize a list */\n// Without initial values\nList<int> nums1 = [];\n// With initial values\nList<int> nums = [1, 3, 2, 5, 4];\n
list.rs
/* Initialize a list */\n// Without initial values\nlet nums1: Vec<i32> = Vec::new();\n// With initial values\nlet nums: Vec<i32> = vec![1, 3, 2, 5, 4];\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Initialize a list */\n// Without initial values\nvar nums1 = listOf<Int>()\n// With initial values\nvar numbers = arrayOf(1, 3, 2, 5, 4)\nvar nums = numbers.toMutableList()\n
list.rb
# Initialize a list\n# Without initial values\nnums1 = []\n# With initial values\nnums = [1, 3, 2, 5, 4]\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#2-access-elements","level":3,"title":"2.   Access Elements","text":"

Since a list is essentially an array, we can access and update elements in \\(O(1)\\) time complexity, which is very efficient.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Access an element\nnum: int = nums[1]  # Access element at index 1\n\n# Update an element\nnums[1] = 0    # Update element at index 1 to 0\n
list.cpp
/* Access an element */\nint num = nums[1];  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Update element at index 1 to 0\n
list.java
/* Access an element */\nint num = nums.get(1);  // Access element at index 1\n\n/* Update an element */\nnums.set(1, 0);  // Update element at index 1 to 0\n
list.cs
/* Access an element */\nint num = nums[1];  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Update element at index 1 to 0\n
list_test.go
/* Access an element */\nnum := nums[1]  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0     // Update element at index 1 to 0\n
list.swift
/* Access an element */\nlet num = nums[1] // Access element at index 1\n\n/* Update an element */\nnums[1] = 0 // Update element at index 1 to 0\n
list.js
/* Access an element */\nconst num = nums[1];  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Update element at index 1 to 0\n
list.ts
/* Access an element */\nconst num: number = nums[1];  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Update element at index 1 to 0\n
list.dart
/* Access an element */\nint num = nums[1];  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Update element at index 1 to 0\n
list.rs
/* Access an element */\nlet num: i32 = nums[1];  // Access element at index 1\n/* Update an element */\nnums[1] = 0;             // Update element at index 1 to 0\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Access an element */\nval num = nums[1]       // Access element at index 1\n/* Update an element */\nnums[1] = 0             // Update element at index 1 to 0\n
list.rb
# Access an element\nnum = nums[1] # Access element at index 1\n# Update an element\nnums[1] = 0 # Update element at index 1 to 0\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#3-insert-and-delete-elements","level":3,"title":"3.   Insert and Delete Elements","text":"

Compared to arrays, lists can freely add and delete elements. Adding an element at the end of a list has a time complexity of \\(O(1)\\), but inserting and deleting elements still have the same efficiency as arrays, with a time complexity of \\(O(n)\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Clear the list\nnums.clear()\n\n# Add elements at the end\nnums.append(1)\nnums.append(3)\nnums.append(2)\nnums.append(5)\nnums.append(4)\n\n# Insert an element in the middle\nnums.insert(3, 6)  # Insert number 6 at index 3\n\n# Delete an element\nnums.pop(3)        # Delete element at index 3\n
list.cpp
/* Clear the list */\nnums.clear();\n\n/* Add elements at the end */\nnums.push_back(1);\nnums.push_back(3);\nnums.push_back(2);\nnums.push_back(5);\nnums.push_back(4);\n\n/* Insert an element in the middle */\nnums.insert(nums.begin() + 3, 6);  // Insert number 6 at index 3\n\n/* Delete an element */\nnums.erase(nums.begin() + 3);      // Delete element at index 3\n
list.java
/* Clear the list */\nnums.clear();\n\n/* Add elements at the end */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.add(3, 6);  // Insert number 6 at index 3\n\n/* Delete an element */\nnums.remove(3);  // Delete element at index 3\n
list.cs
/* Clear the list */\nnums.Clear();\n\n/* Add elements at the end */\nnums.Add(1);\nnums.Add(3);\nnums.Add(2);\nnums.Add(5);\nnums.Add(4);\n\n/* Insert an element in the middle */\nnums.Insert(3, 6);  // Insert number 6 at index 3\n\n/* Delete an element */\nnums.RemoveAt(3);  // Delete element at index 3\n
list_test.go
/* Clear the list */\nnums = nil\n\n/* Add elements at the end */\nnums = append(nums, 1)\nnums = append(nums, 3)\nnums = append(nums, 2)\nnums = append(nums, 5)\nnums = append(nums, 4)\n\n/* Insert an element in the middle */\nnums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Insert number 6 at index 3\n\n/* Delete an element */\nnums = append(nums[:3], nums[4:]...) // Delete element at index 3\n
list.swift
/* Clear the list */\nnums.removeAll()\n\n/* Add elements at the end */\nnums.append(1)\nnums.append(3)\nnums.append(2)\nnums.append(5)\nnums.append(4)\n\n/* Insert an element in the middle */\nnums.insert(6, at: 3) // Insert number 6 at index 3\n\n/* Delete an element */\nnums.remove(at: 3) // Delete element at index 3\n
list.js
/* Clear the list */\nnums.length = 0;\n\n/* Add elements at the end */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.splice(3, 0, 6); // Insert number 6 at index 3\n\n/* Delete an element */\nnums.splice(3, 1);  // Delete element at index 3\n
list.ts
/* Clear the list */\nnums.length = 0;\n\n/* Add elements at the end */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.splice(3, 0, 6); // Insert number 6 at index 3\n\n/* Delete an element */\nnums.splice(3, 1);  // Delete element at index 3\n
list.dart
/* Clear the list */\nnums.clear();\n\n/* Add elements at the end */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.insert(3, 6); // Insert number 6 at index 3\n\n/* Delete an element */\nnums.removeAt(3); // Delete element at index 3\n
list.rs
/* Clear the list */\nnums.clear();\n\n/* Add elements at the end */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.insert(3, 6);  // Insert number 6 at index 3\n\n/* Delete an element */\nnums.remove(3);    // Delete element at index 3\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Clear the list */\nnums.clear();\n\n/* Add elements at the end */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.add(3, 6);  // Insert number 6 at index 3\n\n/* Delete an element */\nnums.remove(3);  // Delete element at index 3\n
list.rb
# Clear the list\nnums.clear\n\n# Add elements at the end\nnums << 1\nnums << 3\nnums << 2\nnums << 5\nnums << 4\n\n# Insert an element in the middle\nnums.insert(3, 6) # Insert number 6 at index 3\n\n# Delete an element\nnums.delete_at(3) # Delete element at index 3\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#4-traverse-a-list","level":3,"title":"4.   Traverse a List","text":"

Like arrays, lists can be traversed by index or by directly iterating through elements.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Traverse the list by index\ncount = 0\nfor i in range(len(nums)):\n    count += nums[i]\n\n# Traverse list elements directly\nfor num in nums:\n    count += num\n
list.cpp
/* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.size(); i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (int num : nums) {\n    count += num;\n}\n
list.java
/* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.size(); i++) {\n    count += nums.get(i);\n}\n\n/* Traverse list elements directly */\nfor (int num : nums) {\n    count += num;\n}\n
list.cs
/* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.Count; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nforeach (int num in nums) {\n    count += num;\n}\n
list_test.go
/* Traverse the list by index */\ncount := 0\nfor i := 0; i < len(nums); i++ {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\ncount = 0\nfor _, num := range nums {\n    count += num\n}\n
list.swift
/* Traverse the list by index */\nvar count = 0\nfor i in nums.indices {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\ncount = 0\nfor num in nums {\n    count += num\n}\n
list.js
/* Traverse the list by index */\nlet count = 0;\nfor (let i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (const num of nums) {\n    count += num;\n}\n
list.ts
/* Traverse the list by index */\nlet count = 0;\nfor (let i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (const num of nums) {\n    count += num;\n}\n
list.dart
/* Traverse the list by index */\nint count = 0;\nfor (var i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (var num in nums) {\n    count += num;\n}\n
list.rs
// Traverse the list by index\nlet mut _count = 0;\nfor i in 0..nums.len() {\n    _count += nums[i];\n}\n\n// Traverse list elements directly\n_count = 0;\nfor num in &nums {\n    _count += num;\n}\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Traverse the list by index */\nvar count = 0\nfor (i in nums.indices) {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\nfor (num in nums) {\n    count += num\n}\n
list.rb
# Traverse the list by index\ncount = 0\nfor i in 0...nums.length\n    count += nums[i]\nend\n\n# Traverse list elements directly\ncount = 0\nfor num in nums\n    count += num\nend\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#5-concatenate-lists","level":3,"title":"5.   Concatenate Lists","text":"

Given a new list nums1, we can concatenate it to the end of the original list.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Concatenate two lists\nnums1: list[int] = [6, 8, 7, 10, 9]\nnums += nums1  # Concatenate list nums1 to the end of nums\n
list.cpp
/* Concatenate two lists */\nvector<int> nums1 = { 6, 8, 7, 10, 9 };\n// Concatenate list nums1 to the end of nums\nnums.insert(nums.end(), nums1.begin(), nums1.end());\n
list.java
/* Concatenate two lists */\nList<Integer> nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 }));\nnums.addAll(nums1);  // Concatenate list nums1 to the end of nums\n
list.cs
/* Concatenate two lists */\nList<int> nums1 = [6, 8, 7, 10, 9];\nnums.AddRange(nums1);  // Concatenate list nums1 to the end of nums\n
list_test.go
/* Concatenate two lists */\nnums1 := []int{6, 8, 7, 10, 9}\nnums = append(nums, nums1...)  // Concatenate list nums1 to the end of nums\n
list.swift
/* Concatenate two lists */\nlet nums1 = [6, 8, 7, 10, 9]\nnums.append(contentsOf: nums1) // Concatenate list nums1 to the end of nums\n
list.js
/* Concatenate two lists */\nconst nums1 = [6, 8, 7, 10, 9];\nnums.push(...nums1);  // Concatenate list nums1 to the end of nums\n
list.ts
/* Concatenate two lists */\nconst nums1: number[] = [6, 8, 7, 10, 9];\nnums.push(...nums1);  // Concatenate list nums1 to the end of nums\n
list.dart
/* Concatenate two lists */\nList<int> nums1 = [6, 8, 7, 10, 9];\nnums.addAll(nums1);  // Concatenate list nums1 to the end of nums\n
list.rs
/* Concatenate two lists */\nlet nums1: Vec<i32> = vec![6, 8, 7, 10, 9];\nnums.extend(nums1);\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Concatenate two lists */\nval nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList()\nnums.addAll(nums1)  // Concatenate list nums1 to the end of nums\n
list.rb
# Concatenate two lists\nnums1 = [6, 8, 7, 10, 9]\nnums += nums1\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#6-sort-a-list","level":3,"title":"6.   Sort a List","text":"

After sorting a list, we can use \"binary search\" and \"two-pointer\" algorithms, which are frequently tested in array algorithm problems.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Sort a list\nnums.sort()  # After sorting, list elements are arranged from smallest to largest\n
list.cpp
/* Sort a list */\nsort(nums.begin(), nums.end());  // After sorting, list elements are arranged from smallest to largest\n
list.java
/* Sort a list */\nCollections.sort(nums);  // After sorting, list elements are arranged from smallest to largest\n
list.cs
/* Sort a list */\nnums.Sort(); // After sorting, list elements are arranged from smallest to largest\n
list_test.go
/* Sort a list */\nsort.Ints(nums)  // After sorting, list elements are arranged from smallest to largest\n
list.swift
/* Sort a list */\nnums.sort() // After sorting, list elements are arranged from smallest to largest\n
list.js
/* Sort a list */\nnums.sort((a, b) => a - b);  // After sorting, list elements are arranged from smallest to largest\n
list.ts
/* Sort a list */\nnums.sort((a, b) => a - b);  // After sorting, list elements are arranged from smallest to largest\n
list.dart
/* Sort a list */\nnums.sort(); // After sorting, list elements are arranged from smallest to largest\n
list.rs
/* Sort a list */\nnums.sort(); // After sorting, list elements are arranged from smallest to largest\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Sort a list */\nnums.sort() // After sorting, list elements are arranged from smallest to largest\n
list.rb
# Sort a list\nnums = nums.sort { |a, b| a <=> b } # After sorting, list elements are arranged from smallest to largest\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#432-list-implementation","level":2,"title":"4.3.2   List Implementation","text":"

Many programming languages have built-in lists, such as Java, C++, and Python. Their implementations are quite complex, and the parameters are carefully considered, such as initial capacity, expansion multiples, and so on. Interested readers can consult the source code to learn more.

To deepen our understanding of how lists work, we attempt to implement a simple list with three key design considerations:

  • Initial capacity: Select a reasonable initial capacity for the underlying array. In this example, we choose 10 as the initial capacity.
  • Size tracking: Declare a variable size to record the current number of elements in the list and update it in real-time as elements are inserted and deleted. Based on this variable, we can locate the end of the list and determine whether expansion is needed.
  • Expansion mechanism: When the list capacity is full upon inserting an element, we need to expand. We create a larger array based on the expansion multiple and then move all elements from the current array to the new array in order. In this example, we specify that the array should be expanded to 2 times its previous size each time.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_list.py
class MyList:\n    \"\"\"List class\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._capacity: int = 10  # List capacity\n        self._arr: list[int] = [0] * self._capacity  # Array (stores list elements)\n        self._size: int = 0  # List length (current number of elements)\n        self._extend_ratio: int = 2  # Multiple by which the list capacity is extended each time\n\n    def size(self) -> int:\n        \"\"\"Get list length (current number of elements)\"\"\"\n        return self._size\n\n    def capacity(self) -> int:\n        \"\"\"Get list capacity\"\"\"\n        return self._capacity\n\n    def get(self, index: int) -> int:\n        \"\"\"Access element\"\"\"\n        # If the index is out of bounds, throw an exception, as below\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        return self._arr[index]\n\n    def set(self, num: int, index: int):\n        \"\"\"Update element\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        self._arr[index] = num\n\n    def add(self, num: int):\n        \"\"\"Add element at the end\"\"\"\n        # When the number of elements exceeds capacity, trigger the extension mechanism\n        if self.size() == self.capacity():\n            self.extend_capacity()\n        self._arr[self._size] = num\n        self._size += 1\n\n    def insert(self, num: int, index: int):\n        \"\"\"Insert element in the middle\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        # When the number of elements exceeds capacity, trigger the extension mechanism\n        if self._size == self.capacity():\n            self.extend_capacity()\n        # Move all elements at and after index index backward by one position\n        for j in range(self._size - 1, index - 1, -1):\n            self._arr[j + 1] = self._arr[j]\n        self._arr[index] = num\n        # Update the number of elements\n        self._size += 1\n\n    def remove(self, index: int) -> int:\n        \"\"\"Remove element\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        num = self._arr[index]\n        # Move all elements after index index forward by one position\n        for j in range(index, self._size - 1):\n            self._arr[j] = self._arr[j + 1]\n        # Update the number of elements\n        self._size -= 1\n        # Return the removed element\n        return num\n\n    def extend_capacity(self):\n        \"\"\"Extend list capacity\"\"\"\n        # Create a new array with length _extend_ratio times the original array, and copy the original array to the new array\n        self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1)\n        # Update list capacity\n        self._capacity = len(self._arr)\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return list with valid length\"\"\"\n        return self._arr[: self._size]\n
my_list.cpp
/* List class */\nclass MyList {\n  private:\n    int *arr;             // Array (stores list elements)\n    int arrCapacity = 10; // List capacity\n    int arrSize = 0;      // List length (current number of elements)\n    int extendRatio = 2;   // Multiple by which the list capacity is extended each time\n\n  public:\n    /* Constructor */\n    MyList() {\n        arr = new int[arrCapacity];\n    }\n\n    /* Destructor */\n    ~MyList() {\n        delete[] arr;\n    }\n\n    /* Get list length (current number of elements)*/\n    int size() {\n        return arrSize;\n    }\n\n    /* Get list capacity */\n    int capacity() {\n        return arrCapacity;\n    }\n\n    /* Update element */\n    int get(int index) {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Add elements at the end */\n    void set(int index, int num) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    void add(int num) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size() == capacity())\n            extendCapacity();\n        arr[size()] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Sort list */\n    void insert(int index, int num) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size() == capacity())\n            extendCapacity();\n        // Move all elements after index index forward by one position\n        for (int j = size() - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Remove element */\n    int remove(int index) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        int num = arr[index];\n        // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array\n        for (int j = index; j < size() - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        arrSize--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    void extendCapacity() {\n        // Create a new array with length extendRatio times the original array\n        int newCapacity = capacity() * extendRatio;\n        int *tmp = arr;\n        arr = new int[newCapacity];\n        // Copy all elements from the original array to the new array\n        for (int i = 0; i < size(); i++) {\n            arr[i] = tmp[i];\n        }\n        // Free memory\n        delete[] tmp;\n        arrCapacity = newCapacity;\n    }\n\n    /* Convert list to Vector for printing */\n    vector<int> toVector() {\n        // Elements enqueue\n        vector<int> vec(size());\n        for (int i = 0; i < size(); i++) {\n            vec[i] = arr[i];\n        }\n        return vec;\n    }\n};\n
my_list.java
/* List class */\nclass MyList {\n    private int[] arr; // Array (stores list elements)\n    private int capacity = 10; // List capacity\n    private int size = 0; // List length (current number of elements)\n    private int extendRatio = 2; // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    public MyList() {\n        arr = new int[capacity];\n    }\n\n    /* Get list length (current number of elements) */\n    public int size() {\n        return size;\n    }\n\n    /* Get list capacity */\n    public int capacity() {\n        return capacity;\n    }\n\n    /* Update element */\n    public int get(int index) {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Add elements at the end */\n    public void set(int index, int num) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    public void add(int num) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size == capacity())\n            extendCapacity();\n        arr[size] = num;\n        // Update the number of elements\n        size++;\n    }\n\n    /* Sort list */\n    public void insert(int index, int num) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size == capacity())\n            extendCapacity();\n        // Move all elements after index index forward by one position\n        for (int j = size - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        size++;\n    }\n\n    /* Remove element */\n    public int remove(int index) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        int num = arr[index];\n        // Move all elements after index forward by one position\n        for (int j = index; j < size - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    public void extendCapacity() {\n        // Create a new array with length extendRatio times the original array and copy the original array to the new array\n        arr = Arrays.copyOf(arr, capacity() * extendRatio);\n        // Add elements at the end\n        capacity = arr.length;\n    }\n\n    /* Convert list to array */\n    public int[] toArray() {\n        int size = size();\n        // Elements enqueue\n        int[] arr = new int[size];\n        for (int i = 0; i < size; i++) {\n            arr[i] = get(i);\n        }\n        return arr;\n    }\n}\n
my_list.cs
/* List class */\nclass MyList {\n    private int[] arr;           // Array (stores list elements)\n    private int arrCapacity = 10;    // List capacity\n    private int arrSize = 0;         // List length (current number of elements)\n    private readonly int extendRatio = 2;  // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    public MyList() {\n        arr = new int[arrCapacity];\n    }\n\n    /* Get list length (current number of elements) */\n    public int Size() {\n        return arrSize;\n    }\n\n    /* Get list capacity */\n    public int Capacity() {\n        return arrCapacity;\n    }\n\n    /* Update element */\n    public int Get(int index) {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Add elements at the end */\n    public void Set(int index, int num) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    public void Add(int num) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (arrSize == arrCapacity)\n            ExtendCapacity();\n        arr[arrSize] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Sort list */\n    public void Insert(int index, int num) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (arrSize == arrCapacity)\n            ExtendCapacity();\n        // Move all elements after index index forward by one position\n        for (int j = arrSize - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Remove element */\n    public int Remove(int index) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        int num = arr[index];\n        // Move all elements after index forward by one position\n        for (int j = index; j < arrSize - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        arrSize--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    public void ExtendCapacity() {\n        // Create new array of length arrCapacity * extendRatio and copy original array to new array\n        Array.Resize(ref arr, arrCapacity * extendRatio);\n        // Add elements at the end\n        arrCapacity = arr.Length;\n    }\n\n    /* Convert list to array */\n    public int[] ToArray() {\n        // Elements enqueue\n        int[] arr = new int[arrSize];\n        for (int i = 0; i < arrSize; i++) {\n            arr[i] = Get(i);\n        }\n        return arr;\n    }\n}\n
my_list.go
/* List class */\ntype myList struct {\n    arrCapacity int\n    arr         []int\n    arrSize     int\n    extendRatio int\n}\n\n/* Constructor */\nfunc newMyList() *myList {\n    return &myList{\n        arrCapacity: 10,              // List capacity\n        arr:         make([]int, 10), // Array (stores list elements)\n        arrSize:     0,               // List length (current number of elements)\n        extendRatio: 2,               // Multiple by which the list capacity is extended each time\n    }\n}\n\n/* Get list length (current number of elements) */\nfunc (l *myList) size() int {\n    return l.arrSize\n}\n\n/* Get list capacity */\nfunc (l *myList) capacity() int {\n    return l.arrCapacity\n}\n\n/* Update element */\nfunc (l *myList) get(index int) int {\n    // If the index is out of bounds, throw an exception, as below\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    return l.arr[index]\n}\n\n/* Add elements at the end */\nfunc (l *myList) set(num, index int) {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    l.arr[index] = num\n}\n\n/* Direct traversal of list elements */\nfunc (l *myList) add(num int) {\n    // When the number of elements exceeds capacity, trigger the extension mechanism\n    if l.arrSize == l.arrCapacity {\n        l.extendCapacity()\n    }\n    l.arr[l.arrSize] = num\n    // Update the number of elements\n    l.arrSize++\n}\n\n/* Sort list */\nfunc (l *myList) insert(num, index int) {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    // When the number of elements exceeds capacity, trigger the extension mechanism\n    if l.arrSize == l.arrCapacity {\n        l.extendCapacity()\n    }\n    // Move all elements after index index forward by one position\n    for j := l.arrSize - 1; j >= index; j-- {\n        l.arr[j+1] = l.arr[j]\n    }\n    l.arr[index] = num\n    // Update the number of elements\n    l.arrSize++\n}\n\n/* Remove element */\nfunc (l *myList) remove(index int) int {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    num := l.arr[index]\n    // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array\n    for j := index; j < l.arrSize-1; j++ {\n        l.arr[j] = l.arr[j+1]\n    }\n    // Update the number of elements\n    l.arrSize--\n    // Return the removed element\n    return num\n}\n\n/* Driver Code */\nfunc (l *myList) extendCapacity() {\n    // Create a new array with length extendRatio times the original array and copy the original array to the new array\n    l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...)\n    // Add elements at the end\n    l.arrCapacity = len(l.arr)\n}\n\n/* Return list with valid length */\nfunc (l *myList) toArray() []int {\n    // Elements enqueue\n    return l.arr[:l.arrSize]\n}\n
my_list.swift
/* List class */\nclass MyList {\n    private var arr: [Int] // Array (stores list elements)\n    private var _capacity: Int // List capacity\n    private var _size: Int // List length (current number of elements)\n    private let extendRatio: Int // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    init() {\n        _capacity = 10\n        _size = 0\n        extendRatio = 2\n        arr = Array(repeating: 0, count: _capacity)\n    }\n\n    /* Get list length (current number of elements) */\n    func size() -> Int {\n        _size\n    }\n\n    /* Get list capacity */\n    func capacity() -> Int {\n        _capacity\n    }\n\n    /* Update element */\n    func get(index: Int) -> Int {\n        // Throw error if index out of bounds, same below\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        return arr[index]\n    }\n\n    /* Add elements at the end */\n    func set(index: Int, num: Int) {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        arr[index] = num\n    }\n\n    /* Direct traversal of list elements */\n    func add(num: Int) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if size() == capacity() {\n            extendCapacity()\n        }\n        arr[size()] = num\n        // Update the number of elements\n        _size += 1\n    }\n\n    /* Sort list */\n    func insert(index: Int, num: Int) {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if size() == capacity() {\n            extendCapacity()\n        }\n        // Move all elements after index index forward by one position\n        for j in (index ..< size()).reversed() {\n            arr[j + 1] = arr[j]\n        }\n        arr[index] = num\n        // Update the number of elements\n        _size += 1\n    }\n\n    /* Remove element */\n    @discardableResult\n    func remove(index: Int) -> Int {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        let num = arr[index]\n        // Move all elements after index forward by one position\n        for j in index ..< (size() - 1) {\n            arr[j] = arr[j + 1]\n        }\n        // Update the number of elements\n        _size -= 1\n        // Return the removed element\n        return num\n    }\n\n    /* Driver Code */\n    func extendCapacity() {\n        // Create a new array with length extendRatio times the original array and copy the original array to the new array\n        arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1))\n        // Add elements at the end\n        _capacity = arr.count\n    }\n\n    /* Convert list to array */\n    func toArray() -> [Int] {\n        Array(arr.prefix(size()))\n    }\n}\n
my_list.js
/* List class */\nclass MyList {\n    #arr = new Array(); // Array (stores list elements)\n    #capacity = 10; // List capacity\n    #size = 0; // List length (current number of elements)\n    #extendRatio = 2; // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    constructor() {\n        this.#arr = new Array(this.#capacity);\n    }\n\n    /* Get list length (current number of elements) */\n    size() {\n        return this.#size;\n    }\n\n    /* Get list capacity */\n    capacity() {\n        return this.#capacity;\n    }\n\n    /* Update element */\n    get(index) {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        return this.#arr[index];\n    }\n\n    /* Add elements at the end */\n    set(index, num) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        this.#arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    add(num) {\n        // If length equals capacity, need to expand\n        if (this.#size === this.#capacity) {\n            this.extendCapacity();\n        }\n        // Add new element to end of list\n        this.#arr[this.#size] = num;\n        this.#size++;\n    }\n\n    /* Sort list */\n    insert(index, num) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (this.#size === this.#capacity) {\n            this.extendCapacity();\n        }\n        // Move all elements after index index forward by one position\n        for (let j = this.#size - 1; j >= index; j--) {\n            this.#arr[j + 1] = this.#arr[j];\n        }\n        // Update the number of elements\n        this.#arr[index] = num;\n        this.#size++;\n    }\n\n    /* Remove element */\n    remove(index) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        let num = this.#arr[index];\n        // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array\n        for (let j = index; j < this.#size - 1; j++) {\n            this.#arr[j] = this.#arr[j + 1];\n        }\n        // Update the number of elements\n        this.#size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    extendCapacity() {\n        // Create a new array with length extendRatio times the original array and copy the original array to the new array\n        this.#arr = this.#arr.concat(\n            new Array(this.capacity() * (this.#extendRatio - 1))\n        );\n        // Add elements at the end\n        this.#capacity = this.#arr.length;\n    }\n\n    /* Convert list to array */\n    toArray() {\n        let size = this.size();\n        // Elements enqueue\n        const arr = new Array(size);\n        for (let i = 0; i < size; i++) {\n            arr[i] = this.get(i);\n        }\n        return arr;\n    }\n}\n
my_list.ts
/* List class */\nclass MyList {\n    private arr: Array<number>; // Array (stores list elements)\n    private _capacity: number = 10; // List capacity\n    private _size: number = 0; // List length (current number of elements)\n    private extendRatio: number = 2; // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    constructor() {\n        this.arr = new Array(this._capacity);\n    }\n\n    /* Get list length (current number of elements) */\n    public size(): number {\n        return this._size;\n    }\n\n    /* Get list capacity */\n    public capacity(): number {\n        return this._capacity;\n    }\n\n    /* Update element */\n    public get(index: number): number {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        return this.arr[index];\n    }\n\n    /* Add elements at the end */\n    public set(index: number, num: number): void {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        this.arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    public add(num: number): void {\n        // If length equals capacity, need to expand\n        if (this._size === this._capacity) this.extendCapacity();\n        // Add new element to end of list\n        this.arr[this._size] = num;\n        this._size++;\n    }\n\n    /* Sort list */\n    public insert(index: number, num: number): void {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (this._size === this._capacity) {\n            this.extendCapacity();\n        }\n        // Move all elements after index index forward by one position\n        for (let j = this._size - 1; j >= index; j--) {\n            this.arr[j + 1] = this.arr[j];\n        }\n        // Update the number of elements\n        this.arr[index] = num;\n        this._size++;\n    }\n\n    /* Remove element */\n    public remove(index: number): number {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        let num = this.arr[index];\n        // Move all elements after index forward by one position\n        for (let j = index; j < this._size - 1; j++) {\n            this.arr[j] = this.arr[j + 1];\n        }\n        // Update the number of elements\n        this._size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    public extendCapacity(): void {\n        // Create new array of length size and copy original array to new array\n        this.arr = this.arr.concat(\n            new Array(this.capacity() * (this.extendRatio - 1))\n        );\n        // Add elements at the end\n        this._capacity = this.arr.length;\n    }\n\n    /* Convert list to array */\n    public toArray(): number[] {\n        let size = this.size();\n        // Elements enqueue\n        const arr = new Array(size);\n        for (let i = 0; i < size; i++) {\n            arr[i] = this.get(i);\n        }\n        return arr;\n    }\n}\n
my_list.dart
/* List class */\nclass MyList {\n  late List<int> _arr; // Array (stores list elements)\n  int _capacity = 10; // List capacity\n  int _size = 0; // List length (current number of elements)\n  int _extendRatio = 2; // Multiple by which the list capacity is extended each time\n\n  /* Constructor */\n  MyList() {\n    _arr = List.filled(_capacity, 0);\n  }\n\n  /* Get list length (current number of elements) */\n  int size() => _size;\n\n  /* Get list capacity */\n  int capacity() => _capacity;\n\n  /* Update element */\n  int get(int index) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    return _arr[index];\n  }\n\n  /* Add elements at the end */\n  void set(int index, int _num) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    _arr[index] = _num;\n  }\n\n  /* Direct traversal of list elements */\n  void add(int _num) {\n    // When the number of elements exceeds capacity, trigger the extension mechanism\n    if (_size == _capacity) extendCapacity();\n    _arr[_size] = _num;\n    // Update the number of elements\n    _size++;\n  }\n\n  /* Sort list */\n  void insert(int index, int _num) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    // When the number of elements exceeds capacity, trigger the extension mechanism\n    if (_size == _capacity) extendCapacity();\n    // Move all elements after index index forward by one position\n    for (var j = _size - 1; j >= index; j--) {\n      _arr[j + 1] = _arr[j];\n    }\n    _arr[index] = _num;\n    // Update the number of elements\n    _size++;\n  }\n\n  /* Remove element */\n  int remove(int index) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    int _num = _arr[index];\n    // Move all elements after index forward by one position\n    for (var j = index; j < _size - 1; j++) {\n      _arr[j] = _arr[j + 1];\n    }\n    // Update the number of elements\n    _size--;\n    // Return the removed element\n    return _num;\n  }\n\n  /* Driver Code */\n  void extendCapacity() {\n    // Create new array with length _extendRatio times original array\n    final _newNums = List.filled(_capacity * _extendRatio, 0);\n    // Copy original array to new array\n    List.copyRange(_newNums, 0, _arr);\n    // Update _arr reference\n    _arr = _newNums;\n    // Add elements at the end\n    _capacity = _arr.length;\n  }\n\n  /* Convert list to array */\n  List<int> toArray() {\n    List<int> arr = [];\n    for (var i = 0; i < _size; i++) {\n      arr.add(get(i));\n    }\n    return arr;\n  }\n}\n
my_list.rs
/* List class */\n#[allow(dead_code)]\nstruct MyList {\n    arr: Vec<i32>,       // Array (stores list elements)\n    capacity: usize,     // List capacity\n    size: usize,         // List length (current number of elements)\n    extend_ratio: usize, // Multiple by which the list capacity is extended each time\n}\n\n#[allow(unused, unused_comparisons)]\nimpl MyList {\n    /* Constructor */\n    pub fn new(capacity: usize) -> Self {\n        let mut vec = vec![0; capacity];\n        Self {\n            arr: vec,\n            capacity,\n            size: 0,\n            extend_ratio: 2,\n        }\n    }\n\n    /* Get list length (current number of elements) */\n    pub fn size(&self) -> usize {\n        return self.size;\n    }\n\n    /* Get list capacity */\n    pub fn capacity(&self) -> usize {\n        return self.capacity;\n    }\n\n    /* Update element */\n    pub fn get(&self, index: usize) -> i32 {\n        // If the index is out of bounds, throw an exception, as below\n        if index >= self.size {\n            panic!(\"Index out of bounds\")\n        };\n        return self.arr[index];\n    }\n\n    /* Add elements at the end */\n    pub fn set(&mut self, index: usize, num: i32) {\n        if index >= self.size {\n            panic!(\"Index out of bounds\")\n        };\n        self.arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    pub fn add(&mut self, num: i32) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if self.size == self.capacity() {\n            self.extend_capacity();\n        }\n        self.arr[self.size] = num;\n        // Update the number of elements\n        self.size += 1;\n    }\n\n    /* Sort list */\n    pub fn insert(&mut self, index: usize, num: i32) {\n        if index >= self.size() {\n            panic!(\"Index out of bounds\")\n        };\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if self.size == self.capacity() {\n            self.extend_capacity();\n        }\n        // Move all elements after index index forward by one position\n        for j in (index..self.size).rev() {\n            self.arr[j + 1] = self.arr[j];\n        }\n        self.arr[index] = num;\n        // Update the number of elements\n        self.size += 1;\n    }\n\n    /* Remove element */\n    pub fn remove(&mut self, index: usize) -> i32 {\n        if index >= self.size() {\n            panic!(\"Index out of bounds\")\n        };\n        let num = self.arr[index];\n        // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array\n        for j in index..self.size - 1 {\n            self.arr[j] = self.arr[j + 1];\n        }\n        // Update the number of elements\n        self.size -= 1;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    pub fn extend_capacity(&mut self) {\n        // Create new array with length extend_ratio times original, copy original array to new array\n        let new_capacity = self.capacity * self.extend_ratio;\n        self.arr.resize(new_capacity, 0);\n        // Add elements at the end\n        self.capacity = new_capacity;\n    }\n\n    /* Convert list to array */\n    pub fn to_array(&self) -> Vec<i32> {\n        // Elements enqueue\n        let mut arr = Vec::new();\n        for i in 0..self.size {\n            arr.push(self.get(i));\n        }\n        arr\n    }\n}\n
my_list.c
/* List class */\ntypedef struct {\n    int *arr;        // Array (stores list elements)\n    int capacity;    // List capacity\n    int size;        // List size\n    int extendRatio; // List expansion multiplier\n} MyList;\n\n/* Constructor */\nMyList *newMyList() {\n    MyList *nums = malloc(sizeof(MyList));\n    nums->capacity = 10;\n    nums->arr = malloc(sizeof(int) * nums->capacity);\n    nums->size = 0;\n    nums->extendRatio = 2;\n    return nums;\n}\n\n/* Destructor */\nvoid delMyList(MyList *nums) {\n    free(nums->arr);\n    free(nums);\n}\n\n/* Get list length */\nint size(MyList *nums) {\n    return nums->size;\n}\n\n/* Get list capacity */\nint capacity(MyList *nums) {\n    return nums->capacity;\n}\n\n/* Update element */\nint get(MyList *nums, int index) {\n    assert(index >= 0 && index < nums->size);\n    return nums->arr[index];\n}\n\n/* Add elements at the end */\nvoid set(MyList *nums, int index, int num) {\n    assert(index >= 0 && index < nums->size);\n    nums->arr[index] = num;\n}\n\n/* Direct traversal of list elements */\nvoid add(MyList *nums, int num) {\n    if (size(nums) == capacity(nums)) {\n        extendCapacity(nums); // Expand capacity\n    }\n    nums->arr[size(nums)] = num;\n    nums->size++;\n}\n\n/* Sort list */\nvoid insert(MyList *nums, int index, int num) {\n    assert(index >= 0 && index < size(nums));\n    // When the number of elements exceeds capacity, trigger the extension mechanism\n    if (size(nums) == capacity(nums)) {\n        extendCapacity(nums); // Expand capacity\n    }\n    for (int i = size(nums); i > index; --i) {\n        nums->arr[i] = nums->arr[i - 1];\n    }\n    nums->arr[index] = num;\n    nums->size++;\n}\n\n/* Remove element */\n// Note: stdio.h occupies the remove keyword\nint removeItem(MyList *nums, int index) {\n    assert(index >= 0 && index < size(nums));\n    int num = nums->arr[index];\n    for (int i = index; i < size(nums) - 1; i++) {\n        nums->arr[i] = nums->arr[i + 1];\n    }\n    nums->size--;\n    return num;\n}\n\n/* Driver Code */\nvoid extendCapacity(MyList *nums) {\n    // Allocate space first\n    int newCapacity = capacity(nums) * nums->extendRatio;\n    int *extend = (int *)malloc(sizeof(int) * newCapacity);\n    int *temp = nums->arr;\n\n    // Copy old data to new data\n    for (int i = 0; i < size(nums); i++)\n        extend[i] = nums->arr[i];\n\n    // Free old data\n    free(temp);\n\n    // Update new data\n    nums->arr = extend;\n    nums->capacity = newCapacity;\n}\n\n/* Convert list to Array for printing */\nint *toArray(MyList *nums) {\n    return nums->arr;\n}\n
my_list.kt
/* List class */\nclass MyList {\n    private var arr: IntArray = intArrayOf() // Array (stores list elements)\n    private var capacity: Int = 10 // List capacity\n    private var size: Int = 0 // List length (current number of elements)\n    private var extendRatio: Int = 2 // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    init {\n        arr = IntArray(capacity)\n    }\n\n    /* Get list length (current number of elements) */\n    fun size(): Int {\n        return size\n    }\n\n    /* Get list capacity */\n    fun capacity(): Int {\n        return capacity\n    }\n\n    /* Update element */\n    fun get(index: Int): Int {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        return arr[index]\n    }\n\n    /* Add elements at the end */\n    fun set(index: Int, num: Int) {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        arr[index] = num\n    }\n\n    /* Direct traversal of list elements */\n    fun add(num: Int) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size == capacity())\n            extendCapacity()\n        arr[size] = num\n        // Update the number of elements\n        size++\n    }\n\n    /* Sort list */\n    fun insert(index: Int, num: Int) {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size == capacity())\n            extendCapacity()\n        // Move all elements after index index forward by one position\n        for (j in size - 1 downTo index)\n            arr[j + 1] = arr[j]\n        arr[index] = num\n        // Update the number of elements\n        size++\n    }\n\n    /* Remove element */\n    fun remove(index: Int): Int {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        val num = arr[index]\n        // Move all elements after index forward by one position\n        for (j in index..<size - 1)\n            arr[j] = arr[j + 1]\n        // Update the number of elements\n        size--\n        // Return the removed element\n        return num\n    }\n\n    /* Driver Code */\n    fun extendCapacity() {\n        // Create a new array with length extendRatio times the original array and copy the original array to the new array\n        arr = arr.copyOf(capacity() * extendRatio)\n        // Add elements at the end\n        capacity = arr.size\n    }\n\n    /* Convert list to array */\n    fun toArray(): IntArray {\n        val size = size()\n        // Elements enqueue\n        val arr = IntArray(size)\n        for (i in 0..<size) {\n            arr[i] = get(i)\n        }\n        return arr\n    }\n}\n
my_list.rb
### List class ###\nclass MyList\n  attr_reader :size       # Get list length (current number of elements)\n  attr_reader :capacity   # Get list capacity\n\n  ### Constructor ###\n  def initialize\n    @capacity = 10\n    @size = 0\n    @extend_ratio = 2\n    @arr = Array.new(capacity)\n  end\n\n  ### Access element ###\n  def get(index)\n    # If the index is out of bounds, throw an exception, as below\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    @arr[index]\n  end\n\n  ### Access element ###\n  def set(index, num)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    @arr[index] = num\n  end\n\n  ### Add element at end ###\n  def add(num)\n    # When the number of elements exceeds capacity, trigger the extension mechanism\n    extend_capacity if size == capacity\n    @arr[size] = num\n\n    # Update the number of elements\n    @size += 1\n  end\n\n  ### Insert element in middle ###\n  def insert(index, num)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n\n    # When the number of elements exceeds capacity, trigger the extension mechanism\n    extend_capacity if size == capacity\n\n    # Move all elements after index index forward by one position\n    for j in (size - 1).downto(index)\n      @arr[j + 1] = @arr[j]\n    end\n    @arr[index] = num\n\n    # Update the number of elements\n    @size += 1\n  end\n\n  ### Delete element ###\n  def remove(index)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    num = @arr[index]\n\n    # Move all elements after index forward by one position\n    for j in index...size\n      @arr[j] = @arr[j + 1]\n    end\n\n    # Update the number of elements\n    @size -= 1\n\n    # Return the removed element\n    num\n  end\n\n  ### Expand list capacity ###\n  def extend_capacity\n    # Create new array with length extend_ratio times original, copy original array to new array\n    arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1))\n    # Add elements at the end\n    @capacity = arr.length\n  end\n\n  ### Convert list to array ###\n  def to_array\n    sz = size\n    # Elements enqueue\n    arr = Array.new(sz)\n    for i in 0...sz\n      arr[i] = get(i)\n    end\n    arr\n  end\nend\n
","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/","level":1,"title":"4.4   Random-Access Memory and Cache *","text":"

In the first two sections of this chapter, we explored arrays and linked lists, two fundamental and important data structures that represent \"contiguous storage\" and \"distributed storage\" as two physical structures, respectively.

In fact, physical structure largely determines the efficiency with which programs utilize memory and cache, which in turn affects the overall performance of algorithmic programs.

","path":["Chapter 4. Array and Linked List","4.4   Random-Access Memory and Cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#441-computer-storage-devices","level":2,"title":"4.4.1   Computer Storage Devices","text":"

Computers include three types of storage devices: hard disk, random-access memory (RAM), and cache memory. The following table shows their different roles and performance characteristics in a computer system.

Table 4-2   Computer Storage Devices

Hard Disk RAM Cache Purpose Long-term storage of data, including operating systems, programs, and files Temporary storage of currently running programs and data being processed Storage of frequently accessed data and instructions to reduce CPU's accesses to memory Volatility Data is not lost after power-off Data is lost after power-off Data is lost after power-off Capacity Large, on the order of terabytes (TB) Small, on the order of gigabytes (GB) Very small, on the order of megabytes (MB) Speed Slow, hundreds to thousands of MB/s Fast, tens of GB/s Very fast, tens to hundreds of GB/s Cost (USD/GB) Inexpensive, fractions of a dollar to a few dollars per GB Expensive, tens to hundreds of dollars per GB Very expensive, priced as part of the CPU package

We can imagine the computer storage system as a pyramid structure as shown in the diagram below. Storage devices closer to the top of the pyramid are faster, have smaller capacity, and are more expensive. This multi-layered design is not by accident, but rather the result of careful consideration by computer scientists and engineers.

  • Hard disk cannot be easily replaced by RAM. First, data in memory is lost after power-off, making it unsuitable for long-term data storage. Second, memory is tens of times more expensive than hard disk, which makes it difficult to popularize in the consumer market.
  • Cache cannot simultaneously achieve large capacity and high speed. As the capacity of L1, L2, and L3 caches increases, their physical size becomes larger, and the physical distance between them and the CPU core increases, resulting in longer data transmission time and higher element access latency. With current technology, the multi-layered cache structure represents the best balance point between capacity, speed, and cost.

Figure 4-9   Computer Storage System

Tip

The storage hierarchy of computers embodies a delicate balance among speed, capacity, and cost. In fact, such trade-offs are common across all industrial fields, requiring us to find the optimal balance point between different advantages and constraints.

In summary, hard disk is used for long-term storage of large amounts of data, RAM is used for temporary storage of data being processed during program execution, and cache is used for storage of frequently accessed data and instructions, to improve program execution efficiency. The three work together to ensure efficient operation of the computer system.

As shown in the diagram below, during program execution, data is read from the hard disk into RAM for CPU computation. Cache can be viewed as part of the CPU, it intelligently loads data from RAM, providing the CPU with high-speed data reading, thereby significantly improving program execution efficiency and reducing reliance on slower RAM.

Figure 4-10   Data Flow Among Hard Disk, RAM, and Cache

","path":["Chapter 4. Array and Linked List","4.4   Random-Access Memory and Cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#442-memory-efficiency-of-data-structures","level":2,"title":"4.4.2   Memory Efficiency of Data Structures","text":"

In terms of memory space utilization, arrays and linked lists each have advantages and limitations.

On one hand, memory is limited, and the same memory cannot be shared by multiple programs, so we hope data structures can utilize space as efficiently as possible. Array elements are tightly packed and do not require additional space to store references (pointers) between linked list nodes, thus having higher space efficiency. However, arrays need to allocate sufficient contiguous memory space at once, which may lead to memory waste, and array expansion requires additional time and space costs. In comparison, linked lists perform dynamic memory allocation and deallocation on a \"node\" basis, providing greater flexibility.

On the other hand, during program execution, as memory is repeatedly allocated and freed, the degree of fragmentation of free memory becomes increasingly severe, leading to reduced memory utilization efficiency. Arrays, due to their contiguous storage approach, are relatively less prone to memory fragmentation. Conversely, linked list elements are distributed in storage, and frequent insertion and deletion operations are more likely to cause memory fragmentation.

","path":["Chapter 4. Array and Linked List","4.4   Random-Access Memory and Cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#443-cache-efficiency-of-data-structures","level":2,"title":"4.4.3   Cache Efficiency of Data Structures","text":"

Although cache has much smaller space capacity than memory, it is much faster than memory and plays a crucial role in program execution speed. Since cache capacity is limited and can only store a small portion of frequently accessed data, when the CPU attempts to access data that is not in the cache, a cache miss occurs, and the CPU must load the required data from the slower memory.

Clearly, the fewer \"cache misses,\" the higher the efficiency of CPU data reads and writes, and the better the program performance. We call the proportion of data that the CPU successfully obtains from the cache the cache hit rate, a metric typically used to measure cache efficiency.

To achieve the highest efficiency possible, cache employs the following data loading mechanisms.

  • Cache lines: The cache does not store and load data on a byte-by-byte basis, but rather as cache lines. Compared to byte-by-byte transmission, cache line transmission is more efficient.
  • Prefetching mechanism: The processor attempts to predict data access patterns (e.g., sequential access, fixed-stride jumping access, etc.) and loads data into the cache according to specific patterns, thereby improving hit rate.
  • Spatial locality: If a piece of data is accessed, nearby data may also be accessed in the near future. Therefore, when the cache loads a particular piece of data, it also loads nearby data to improve hit rate.
  • Temporal locality: If a piece of data is accessed, it is likely to be accessed again in the near future. Cache leverages this principle by retaining recently accessed data to improve hit rate.

In fact, arrays and linked lists have different efficiencies in utilizing cache, manifested in the following aspects.

  • Space occupied: Linked list elements occupy more space than array elements, resulting in fewer effective data in the cache.
  • Cache lines: Linked list data are scattered throughout memory, while cache loads \"by lines,\" so the proportion of invalid data loaded is higher.
  • Prefetching mechanism: Arrays have more \"predictable\" data access patterns than linked lists, making it easier for the system to guess which data will be loaded next.
  • Spatial locality: Arrays are stored in centralized memory space, so data near loaded data is more likely to be accessed soon.

Overall, arrays have higher cache hit rates, thus they usually outperform linked lists in operation efficiency. This makes data structures implemented based on arrays more popular when solving algorithmic problems.

It is important to note that high cache efficiency does not mean arrays are superior to linked lists in all cases. In practical applications, which data structure to choose should be determined based on specific requirements. For example, both arrays and linked lists can implement the \"stack\" data structure (which will be discussed in detail in the next chapter), but they are suitable for different scenarios.

  • When solving algorithm problems, we tend to prefer stack implementations based on arrays, because they provide higher operation efficiency and the ability of random access, at the cost of needing to pre-allocate a certain amount of memory space for the array.
  • If the data volume is very large, the dynamic nature is high, and the expected size of the stack is difficult to estimate, then a stack implementation based on linked lists is more suitable. Linked lists can distribute large amounts of data across different parts of memory and avoid the additional overhead produced by array expansion.
","path":["Chapter 4. Array and Linked List","4.4   Random-Access Memory and Cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/","level":1,"title":"4.5   Summary","text":"","path":["Chapter 4. Array and Linked List","4.5   Summary"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Arrays and linked lists are two fundamental data structures, representing two different ways data can be stored in computer memory: contiguous memory storage and scattered memory storage. The characteristics of the two complement each other.
  • Arrays support random access and use less memory; however, inserting and deleting elements is inefficient, and the length is immutable after initialization.
  • Linked lists achieve efficient insertion and deletion of nodes by modifying references (pointers), and can flexibly adjust length; however, node access is inefficient and memory consumption is higher. Common linked list types include singly linked lists, circular linked lists, and doubly linked lists.
  • A list is an ordered collection of elements that supports insertion, deletion, search, and modification, typically implemented based on dynamic arrays. It retains the advantages of arrays while allowing flexible adjustment of length.
  • The emergence of lists has greatly improved the practicality of arrays, but may result in some wasted memory space.
  • During program execution, data is primarily stored in memory. Arrays provide higher memory space efficiency, while linked lists offer greater flexibility in memory usage.
  • Caches provide fast data access to the CPU through mechanisms such as cache lines, prefetching, and spatial and temporal locality, significantly improving program execution efficiency.
  • Because arrays have higher cache hit rates, they are generally more efficient than linked lists. When choosing a data structure, appropriate selection should be made based on specific requirements and scenarios.
","path":["Chapter 4. Array and Linked List","4.5   Summary"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Does storing an array on the stack versus on the heap affect time efficiency and space efficiency?

Arrays stored on the stack and on the heap are both stored in contiguous memory space, so data operation efficiency is basically the same. However, the stack and heap have their own characteristics, leading to the following differences.

  1. Allocation and deallocation efficiency: The stack is a relatively small piece of memory, with allocation automatically handled by the compiler; the heap is relatively larger and can be dynamically allocated in code, more prone to fragmentation. Therefore, allocation and deallocation operations on the heap are usually slower than on the stack.
  2. Size limitations: Stack memory is relatively small, and the heap size is generally limited by available memory. Therefore, the heap is more suitable for storing large arrays.
  3. Flexibility: The size of an array on the stack must be determined at compile time, while the size of an array on the heap can be determined dynamically at runtime.

Q: Why do arrays require elements of the same type, while linked lists do not emphasize this requirement?

Linked lists are composed of nodes, with nodes connected through references (pointers), and each node can store different types of data, such as int, double, string, object, etc.

In contrast, array elements must be of the same type, so that the corresponding element position can be obtained by calculating the offset. For example, if an array contains both int and long types, with individual elements occupying 4 bytes and 8 bytes respectively, then the following formula cannot be used to calculate the offset, because the array contains two different \"element lengths\".

# Element Memory Address = Array Memory Address (first Element Memory address) + Element Length * Element Index\n

Q: After deleting node P, do we need to set P.next to None?

It is not necessary to modify P.next. From the perspective of the linked list, traversing from the head node to the tail node will no longer encounter P. This means that node P has been removed from the linked list, and it doesn't matter where node P points to at this time—it won't affect the linked list.

From a data structures and algorithms perspective (problem-solving), not disconnecting the pointer doesn't matter as long as the program logic is correct. From the perspective of standard libraries, disconnecting is safer and the logic is clearer. If not disconnected, assuming the deleted node is not properly reclaimed, it may affect the memory reclamation of its successor nodes.

Q: In a linked list, the time complexity of insertion and deletion operations is \\(O(1)\\). However, both insertion and deletion require \\(O(n)\\) time to find the element; why isn't the time complexity \\(O(n)\\)?

If the element is first found and then deleted, the time complexity is indeed \\(O(n)\\). However, the advantage of \\(O(1)\\) insertion and deletion in linked lists can be demonstrated in other applications. For example, a deque is well-suited for linked list implementation, where we maintain pointer variables always pointing to the head and tail nodes, with each insertion and deletion operation being \\(O(1)\\).

Q: In the diagram \"Linked List Definition and Storage Methods\", does the light blue pointer node occupy a single memory address, or does it share equally with the node value?

This diagram is a qualitative representation; a quantitative representation requires analysis based on the specific situation.

  • Different types of node values occupy different amounts of space, such as int, long, double, and instance objects, etc.
  • The amount of memory space occupied by pointer variables depends on the operating system and compilation environment used, usually 8 bytes or 4 bytes.

Q: Is appending an element at the end of a list always \\(O(1)\\)?

If appending an element exceeds the list length, the list must first be expanded before adding. The system allocates a new block of memory and moves all elements from the original list to it, in which case the time complexity becomes \\(O(n)\\).

Q: \"The emergence of lists has greatly improved the practicality of arrays, but may result in some wasted memory space\"—does this space waste refer to the memory occupied by additional variables such as capacity, length, and expansion factor?

This space waste mainly has two aspects: on one hand, lists typically set an initial length, which we may not need to fully utilize; on the other hand, to prevent frequent expansion, expansion generally multiplies by a coefficient, such as \\(\\times 1.5\\). As a result, there will be many empty positions that we typically cannot completely fill.

Q: In Python, after initializing n = [1, 2, 3], the addresses of these 3 elements are contiguous, but initializing m = [2, 1, 3] reveals that each element's id is not continuous; rather, they are the same as those in n. Since the addresses of these elements are not contiguous, is m still an array?

If we replace list elements with linked list nodes n = [n1, n2, n3, n4, n5], usually these 5 node objects are also scattered throughout memory. However, given a list index, we can still obtain the node memory address in \\(O(1)\\) time, thereby accessing the corresponding node. This is because the array stores references to nodes, not the nodes themselves.

Unlike many languages, numbers in Python are wrapped as objects, and lists store not the numbers themselves, but references to the numbers. Therefore, we find that the same numbers in two arrays have the same id, and the memory addresses of these numbers need not be contiguous.

Q: C++ STL has std::list which has already implemented a doubly linked list, but it seems that some algorithm books don't use it directly. Is there a limitation?

On one hand, we often prefer to use arrays for implementing algorithms and only use linked lists when necessary, mainly for two reasons.

  • Space overhead: Since each element requires two additional pointers (one for the previous element and one for the next element), std::list typically consumes more space than std::vector.
  • Cache unfriendliness: Since data is not stored contiguously, std::list has lower cache utilization. In general, std::vector has better performance.

On the other hand, cases where linked lists are necessary mainly involve binary trees and graphs. Stacks and queues usually use the stack and queue provided by the programming language, rather than linked lists.

Q: Does the operation res = [[0]] * n create a 2D list where each [0] is independent?

No, they are not independent. In this 2D list, all the [0] are actually references to the same object. If we modify one element, we will find that all corresponding elements change accordingly.

If we want each [0] in the 2D list to be independent, we can use res = [[0] for _ in range(n)] to achieve this. The principle of this approach is to initialize \\(n\\) independent [0] list objects.

Q: Does the operation res = [0] * n create a list where each integer 0 is independent?

In this list, all integer 0s are references to the same object. This is because Python uses a caching mechanism for small integers (typically -5 to 256) to maximize object reuse and improve performance.

Although they point to the same object, we can still independently modify each element in the list. This is because Python integers are \"immutable objects\". When we modify an element, we are actually switching to a reference of another object, rather than changing the original object itself.

However, when list elements are \"mutable objects\" (such as lists, dictionaries, or class instances), modifying an element directly changes the object itself, and all elements referencing that object will have the same change.

","path":["Chapter 4. Array and Linked List","4.5   Summary"],"tags":[]},{"location":"chapter_backtracking/","level":1,"title":"Chapter 13.   Backtracking","text":"

Abstract

We are like explorers in a maze, and may encounter difficulties on the path forward.

The power of backtracking allows us to start over, keep trying, and eventually find the exit leading to light.

","path":["Chapter 13. Backtracking","Chapter 13.   Backtracking"],"tags":[]},{"location":"chapter_backtracking/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 13.1   Backtracking Algorithm
  • 13.2   Permutations Problem
  • 13.3   Subset-Sum Problem
  • 13.4   N-Queens Problem
  • 13.5   Summary
","path":["Chapter 13. Backtracking","Chapter 13.   Backtracking"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/","level":1,"title":"13.1   Backtracking Algorithm","text":"

The backtracking algorithm is a method for solving problems through exhaustive search. Its core idea is to start from an initial state and exhaustively search all possible solutions. When a correct solution is found, it is recorded. This process continues until a solution is found or all possible choices have been tried without finding a solution.

The backtracking algorithm typically employs \"depth-first search\" to traverse the solution space. In the \"Binary Tree\" chapter, we mentioned that preorder, inorder, and postorder traversals all belong to depth-first search. Next, we will construct a backtracking problem using preorder traversal to progressively understand how the backtracking algorithm works.

Example 1

Given a binary tree, search and record all nodes with value \\(7\\), and return a list of these nodes.

For this problem, we perform a preorder traversal of the tree and check whether the current node's value is \\(7\\). If it is, we add the node to the result list res. The relevant implementation is shown in the following figure and code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_i_compact.py
def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: Example 1\"\"\"\n    if root is None:\n        return\n    if root.val == 7:\n        # Record solution\n        res.append(root)\n    pre_order(root.left)\n    pre_order(root.right)\n
preorder_traversal_i_compact.cpp
/* Preorder traversal: Example 1 */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr) {\n        return;\n    }\n    if (root->val == 7) {\n        // Record solution\n        res.push_back(root);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n}\n
preorder_traversal_i_compact.java
/* Preorder traversal: Example 1 */\nvoid preOrder(TreeNode root) {\n    if (root == null) {\n        return;\n    }\n    if (root.val == 7) {\n        // Record solution\n        res.add(root);\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n}\n
preorder_traversal_i_compact.cs
/* Preorder traversal: Example 1 */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) {\n        return;\n    }\n    if (root.val == 7) {\n        // Record solution\n        res.Add(root);\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n}\n
preorder_traversal_i_compact.go
/* Preorder traversal: Example 1 */\nfunc preOrderI(root *TreeNode, res *[]*TreeNode) {\n    if root == nil {\n        return\n    }\n    if (root.Val).(int) == 7 {\n        // Record solution\n        *res = append(*res, root)\n    }\n    preOrderI(root.Left, res)\n    preOrderI(root.Right, res)\n}\n
preorder_traversal_i_compact.swift
/* Preorder traversal: Example 1 */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    if root.val == 7 {\n        // Record solution\n        res.append(root)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n}\n
preorder_traversal_i_compact.js
/* Preorder traversal: Example 1 */\nfunction preOrder(root, res) {\n    if (root === null) {\n        return;\n    }\n    if (root.val === 7) {\n        // Record solution\n        res.push(root);\n    }\n    preOrder(root.left, res);\n    preOrder(root.right, res);\n}\n
preorder_traversal_i_compact.ts
/* Preorder traversal: Example 1 */\nfunction preOrder(root: TreeNode | null, res: TreeNode[]): void {\n    if (root === null) {\n        return;\n    }\n    if (root.val === 7) {\n        // Record solution\n        res.push(root);\n    }\n    preOrder(root.left, res);\n    preOrder(root.right, res);\n}\n
preorder_traversal_i_compact.dart
/* Preorder traversal: Example 1 */\nvoid preOrder(TreeNode? root, List<TreeNode> res) {\n  if (root == null) {\n    return;\n  }\n  if (root.val == 7) {\n    // Record solution\n    res.add(root);\n  }\n  preOrder(root.left, res);\n  preOrder(root.right, res);\n}\n
preorder_traversal_i_compact.rs
/* Preorder traversal: Example 1 */\nfn pre_order(res: &mut Vec<Rc<RefCell<TreeNode>>>, root: Option<&Rc<RefCell<TreeNode>>>) {\n    if root.is_none() {\n        return;\n    }\n    if let Some(node) = root {\n        if node.borrow().val == 7 {\n            // Record solution\n            res.push(node.clone());\n        }\n        pre_order(res, node.borrow().left.as_ref());\n        pre_order(res, node.borrow().right.as_ref());\n    }\n}\n
preorder_traversal_i_compact.c
/* Preorder traversal: Example 1 */\nvoid preOrder(TreeNode *root) {\n    if (root == NULL) {\n        return;\n    }\n    if (root->val == 7) {\n        // Record solution\n        res[resSize++] = root;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n}\n
preorder_traversal_i_compact.kt
/* Preorder traversal: Example 1 */\nfun preOrder(root: TreeNode?) {\n    if (root == null) {\n        return\n    }\n    if (root._val == 7) {\n        // Record solution\n        res!!.add(root)\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n}\n
preorder_traversal_i_compact.rb
### Pre-order traversal: example 1 ###\ndef pre_order(root)\n  return unless root\n\n  # Record solution\n  $res << root if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\nend\n

Figure 13-1   Search for nodes in preorder traversal

","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1311-attempt-and-backtrack","level":2,"title":"13.1.1   Attempt and Backtrack","text":"

The reason it is called a backtracking algorithm is that it employs \"attempt\" and \"backtrack\" strategies when searching the solution space. When the algorithm encounters a state where it cannot continue forward or cannot find a solution that satisfies the constraints, it will undo the previous choice, return to a previous state, and try other possible choices.

For Example 1, visiting each node represents an \"attempt\", while skipping over a leaf node or a function return from the parent node represents a \"backtrack\".

It is worth noting that backtracking is not limited to function returns alone. To illustrate this, let's extend Example 1 slightly.

Example 2

In a binary tree, search all nodes with value \\(7\\), and return the paths from the root node to these nodes.

Based on the code from Example 1, we need to use a list path to record the visited node path. When we reach a node with value \\(7\\), we copy path and add it to the result list res. After traversal is complete, res contains all the solutions. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_ii_compact.py
def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: Example 2\"\"\"\n    if root is None:\n        return\n    # Attempt\n    path.append(root)\n    if root.val == 7:\n        # Record solution\n        res.append(list(path))\n    pre_order(root.left)\n    pre_order(root.right)\n    # Backtrack\n    path.pop()\n
preorder_traversal_ii_compact.cpp
/* Preorder traversal: Example 2 */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr) {\n        return;\n    }\n    // Attempt\n    path.push_back(root);\n    if (root->val == 7) {\n        // Record solution\n        res.push_back(path);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    path.pop_back();\n}\n
preorder_traversal_ii_compact.java
/* Preorder traversal: Example 2 */\nvoid preOrder(TreeNode root) {\n    if (root == null) {\n        return;\n    }\n    // Attempt\n    path.add(root);\n    if (root.val == 7) {\n        // Record solution\n        res.add(new ArrayList<>(path));\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n    // Backtrack\n    path.remove(path.size() - 1);\n}\n
preorder_traversal_ii_compact.cs
/* Preorder traversal: Example 2 */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) {\n        return;\n    }\n    // Attempt\n    path.Add(root);\n    if (root.val == 7) {\n        // Record solution\n        res.Add(new List<TreeNode>(path));\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n    // Backtrack\n    path.RemoveAt(path.Count - 1);\n}\n
preorder_traversal_ii_compact.go
/* Preorder traversal: Example 2 */\nfunc preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {\n    if root == nil {\n        return\n    }\n    // Attempt\n    *path = append(*path, root)\n    if root.Val.(int) == 7 {\n        // Record solution\n        *res = append(*res, append([]*TreeNode{}, *path...))\n    }\n    preOrderII(root.Left, res, path)\n    preOrderII(root.Right, res, path)\n    // Backtrack\n    *path = (*path)[:len(*path)-1]\n}\n
preorder_traversal_ii_compact.swift
/* Preorder traversal: Example 2 */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Attempt\n    path.append(root)\n    if root.val == 7 {\n        // Record solution\n        res.append(path)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n    // Backtrack\n    path.removeLast()\n}\n
preorder_traversal_ii_compact.js
/* Preorder traversal: Example 2 */\nfunction preOrder(root, path, res) {\n    if (root === null) {\n        return;\n    }\n    // Attempt\n    path.push(root);\n    if (root.val === 7) {\n        // Record solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
preorder_traversal_ii_compact.ts
/* Preorder traversal: Example 2 */\nfunction preOrder(\n    root: TreeNode | null,\n    path: TreeNode[],\n    res: TreeNode[][]\n): void {\n    if (root === null) {\n        return;\n    }\n    // Attempt\n    path.push(root);\n    if (root.val === 7) {\n        // Record solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
preorder_traversal_ii_compact.dart
/* Preorder traversal: Example 2 */\nvoid preOrder(\n  TreeNode? root,\n  List<TreeNode> path,\n  List<List<TreeNode>> res,\n) {\n  if (root == null) {\n    return;\n  }\n\n  // Attempt\n  path.add(root);\n  if (root.val == 7) {\n    // Record solution\n    res.add(List.from(path));\n  }\n  preOrder(root.left, path, res);\n  preOrder(root.right, path, res);\n  // Backtrack\n  path.removeLast();\n}\n
preorder_traversal_ii_compact.rs
/* Preorder traversal: Example 2 */\nfn pre_order(\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n    path: &mut Vec<Rc<RefCell<TreeNode>>>,\n    root: Option<&Rc<RefCell<TreeNode>>>,\n) {\n    if root.is_none() {\n        return;\n    }\n    if let Some(node) = root {\n        // Attempt\n        path.push(node.clone());\n        if node.borrow().val == 7 {\n            // Record solution\n            res.push(path.clone());\n        }\n        pre_order(res, path, node.borrow().left.as_ref());\n        pre_order(res, path, node.borrow().right.as_ref());\n        // Backtrack\n        path.pop();\n    }\n}\n
preorder_traversal_ii_compact.c
/* Preorder traversal: Example 2 */\nvoid preOrder(TreeNode *root) {\n    if (root == NULL) {\n        return;\n    }\n    // Attempt\n    path[pathSize++] = root;\n    if (root->val == 7) {\n        // Record solution\n        for (int i = 0; i < pathSize; ++i) {\n            res[resSize][i] = path[i];\n        }\n        resSize++;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    pathSize--;\n}\n
preorder_traversal_ii_compact.kt
/* Preorder traversal: Example 2 */\nfun preOrder(root: TreeNode?) {\n    if (root == null) {\n        return\n    }\n    // Attempt\n    path!!.add(root)\n    if (root._val == 7) {\n        // Record solution\n        res!!.add(path!!.toMutableList())\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n    // Backtrack\n    path!!.removeAt(path!!.size - 1)\n}\n
preorder_traversal_ii_compact.rb
### Pre-order traversal: example 2 ###\ndef pre_order(root)\n  return unless root\n\n  # Attempt\n  $path << root\n\n  # Record solution\n  $res << $path.dup if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\n\n  # Backtrack\n  $path.pop\nend\n

In each \"attempt\", we record the path by adding the current node to path; before \"backtracking\", we need to remove the node from path, to restore the state before this attempt.

Observing the process shown in the following figure, we can understand attempt and backtrack as \"advance\" and \"undo\", two operations that are the reverse of each other.

<1><2><3><4><5><6><7><8><9><10><11>

Figure 13-2   Attempt and backtrack

","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1312-pruning","level":2,"title":"13.1.2   Pruning","text":"

Complex backtracking problems usually contain one or more constraints. Constraints can typically be used for \"pruning\".

Example 3

In a binary tree, search all nodes with value \\(7\\) and return the paths from the root node to these nodes, but require that the paths do not contain nodes with value \\(3\\).

To satisfy the above constraints, we need to add pruning operations: during the search process, if we encounter a node with value \\(3\\), we return early and do not continue searching. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_iii_compact.py
def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: Example 3\"\"\"\n    # Pruning\n    if root is None or root.val == 3:\n        return\n    # Attempt\n    path.append(root)\n    if root.val == 7:\n        # Record solution\n        res.append(list(path))\n    pre_order(root.left)\n    pre_order(root.right)\n    # Backtrack\n    path.pop()\n
preorder_traversal_iii_compact.cpp
/* Preorder traversal: Example 3 */\nvoid preOrder(TreeNode *root) {\n    // Pruning\n    if (root == nullptr || root->val == 3) {\n        return;\n    }\n    // Attempt\n    path.push_back(root);\n    if (root->val == 7) {\n        // Record solution\n        res.push_back(path);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    path.pop_back();\n}\n
preorder_traversal_iii_compact.java
/* Preorder traversal: Example 3 */\nvoid preOrder(TreeNode root) {\n    // Pruning\n    if (root == null || root.val == 3) {\n        return;\n    }\n    // Attempt\n    path.add(root);\n    if (root.val == 7) {\n        // Record solution\n        res.add(new ArrayList<>(path));\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n    // Backtrack\n    path.remove(path.size() - 1);\n}\n
preorder_traversal_iii_compact.cs
/* Preorder traversal: Example 3 */\nvoid PreOrder(TreeNode? root) {\n    // Pruning\n    if (root == null || root.val == 3) {\n        return;\n    }\n    // Attempt\n    path.Add(root);\n    if (root.val == 7) {\n        // Record solution\n        res.Add(new List<TreeNode>(path));\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n    // Backtrack\n    path.RemoveAt(path.Count - 1);\n}\n
preorder_traversal_iii_compact.go
/* Preorder traversal: Example 3 */\nfunc preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {\n    // Pruning\n    if root == nil || root.Val == 3 {\n        return\n    }\n    // Attempt\n    *path = append(*path, root)\n    if root.Val.(int) == 7 {\n        // Record solution\n        *res = append(*res, append([]*TreeNode{}, *path...))\n    }\n    preOrderIII(root.Left, res, path)\n    preOrderIII(root.Right, res, path)\n    // Backtrack\n    *path = (*path)[:len(*path)-1]\n}\n
preorder_traversal_iii_compact.swift
/* Preorder traversal: Example 3 */\nfunc preOrder(root: TreeNode?) {\n    // Pruning\n    guard let root = root, root.val != 3 else {\n        return\n    }\n    // Attempt\n    path.append(root)\n    if root.val == 7 {\n        // Record solution\n        res.append(path)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n    // Backtrack\n    path.removeLast()\n}\n
preorder_traversal_iii_compact.js
/* Preorder traversal: Example 3 */\nfunction preOrder(root, path, res) {\n    // Pruning\n    if (root === null || root.val === 3) {\n        return;\n    }\n    // Attempt\n    path.push(root);\n    if (root.val === 7) {\n        // Record solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
preorder_traversal_iii_compact.ts
/* Preorder traversal: Example 3 */\nfunction preOrder(\n    root: TreeNode | null,\n    path: TreeNode[],\n    res: TreeNode[][]\n): void {\n    // Pruning\n    if (root === null || root.val === 3) {\n        return;\n    }\n    // Attempt\n    path.push(root);\n    if (root.val === 7) {\n        // Record solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
preorder_traversal_iii_compact.dart
/* Preorder traversal: Example 3 */\nvoid preOrder(\n  TreeNode? root,\n  List<TreeNode> path,\n  List<List<TreeNode>> res,\n) {\n  if (root == null || root.val == 3) {\n    return;\n  }\n\n  // Attempt\n  path.add(root);\n  if (root.val == 7) {\n    // Record solution\n    res.add(List.from(path));\n  }\n  preOrder(root.left, path, res);\n  preOrder(root.right, path, res);\n  // Backtrack\n  path.removeLast();\n}\n
preorder_traversal_iii_compact.rs
/* Preorder traversal: Example 3 */\nfn pre_order(\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n    path: &mut Vec<Rc<RefCell<TreeNode>>>,\n    root: Option<&Rc<RefCell<TreeNode>>>,\n) {\n    // Pruning\n    if root.is_none() || root.as_ref().unwrap().borrow().val == 3 {\n        return;\n    }\n    if let Some(node) = root {\n        // Attempt\n        path.push(node.clone());\n        if node.borrow().val == 7 {\n            // Record solution\n            res.push(path.clone());\n        }\n        pre_order(res, path, node.borrow().left.as_ref());\n        pre_order(res, path, node.borrow().right.as_ref());\n        // Backtrack\n        path.pop();\n    }\n}\n
preorder_traversal_iii_compact.c
/* Preorder traversal: Example 3 */\nvoid preOrder(TreeNode *root) {\n    // Pruning\n    if (root == NULL || root->val == 3) {\n        return;\n    }\n    // Attempt\n    path[pathSize++] = root;\n    if (root->val == 7) {\n        // Record solution\n        for (int i = 0; i < pathSize; i++) {\n            res[resSize][i] = path[i];\n        }\n        resSize++;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    pathSize--;\n}\n
preorder_traversal_iii_compact.kt
/* Preorder traversal: Example 3 */\nfun preOrder(root: TreeNode?) {\n    // Pruning\n    if (root == null || root._val == 3) {\n        return\n    }\n    // Attempt\n    path!!.add(root)\n    if (root._val == 7) {\n        // Record solution\n        res!!.add(path!!.toMutableList())\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n    // Backtrack\n    path!!.removeAt(path!!.size - 1)\n}\n
preorder_traversal_iii_compact.rb
### Pre-order traversal: example 3 ###\ndef pre_order(root)\n  # Pruning\n  return if !root || root.val == 3\n\n  # Attempt\n  $path.append(root)\n\n  # Record solution\n  $res << $path.dup if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\n\n  # Backtrack\n  $path.pop\nend\n

\"Pruning\" is a vivid term. As shown in the following figure, during the search process, we \"prune\" search branches that do not satisfy the constraints, avoiding many meaningless attempts and thus improving search efficiency.

Figure 13-3   Pruning according to constraints

","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1313-framework-code","level":2,"title":"13.1.3   Framework Code","text":"

Next, we attempt to extract the main framework of backtracking's \"attempt, backtrack, and pruning\", to improve code generality.

In the following framework code, state represents the current state of the problem, and choices represents the choices available in the current state:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
def backtrack(state: State, choices: list[choice], res: list[state]):\n    \"\"\"Backtracking algorithm framework\"\"\"\n    # Check if it is a solution\n    if is_solution(state):\n        # Record the solution\n        record_solution(state, res)\n        # Stop searching\n        return\n    # Traverse all choices\n    for choice in choices:\n        # Pruning: check if the choice is valid\n        if is_valid(state, choice):\n            # Attempt: make a choice and update the state\n            make_choice(state, choice)\n            backtrack(state, choices, res)\n            # Backtrack: undo the choice and restore to the previous state\n            undo_choice(state, choice)\n
/* Backtracking algorithm framework */\nvoid backtrack(State *state, vector<Choice *> &choices, vector<State *> &res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for (Choice choice : choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nvoid backtrack(State state, List<Choice> choices, List<State> res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for (Choice choice : choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nvoid Backtrack(State state, List<Choice> choices, List<State> res) {\n    // Check if it is a solution\n    if (IsSolution(state)) {\n        // Record the solution\n        RecordSolution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    foreach (Choice choice in choices) {\n        // Pruning: check if the choice is valid\n        if (IsValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            MakeChoice(state, choice);\n            Backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            UndoChoice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nfunc backtrack(state *State, choices []Choice, res *[]State) {\n    // Check if it is a solution\n    if isSolution(state) {\n        // Record the solution\n        recordSolution(state, res)\n        // Stop searching\n        return\n    }\n    // Traverse all choices\n    for _, choice := range choices {\n        // Pruning: check if the choice is valid\n        if isValid(state, choice) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice)\n            backtrack(state, choices, res)\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nfunc backtrack(state: inout State, choices: [Choice], res: inout [State]) {\n    // Check if it is a solution\n    if isSolution(state: state) {\n        // Record the solution\n        recordSolution(state: state, res: &res)\n        // Stop searching\n        return\n    }\n    // Traverse all choices\n    for choice in choices {\n        // Pruning: check if the choice is valid\n        if isValid(state: state, choice: choice) {\n            // Attempt: make a choice and update the state\n            makeChoice(state: &state, choice: choice)\n            backtrack(state: &state, choices: choices, res: &res)\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state: &state, choice: choice)\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nfunction backtrack(state, choices, res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for (let choice of choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nfunction backtrack(state: State, choices: Choice[], res: State[]): void {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for (let choice of choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nvoid backtrack(State state, List<Choice>, List<State> res) {\n  // Check if it is a solution\n  if (isSolution(state)) {\n    // Record the solution\n    recordSolution(state, res);\n    // Stop searching\n    return;\n  }\n  // Traverse all choices\n  for (Choice choice in choices) {\n    // Pruning: check if the choice is valid\n    if (isValid(state, choice)) {\n      // Attempt: make a choice and update the state\n      makeChoice(state, choice);\n      backtrack(state, choices, res);\n      // Backtrack: undo the choice and restore to the previous state\n      undoChoice(state, choice);\n    }\n  }\n}\n
/* Backtracking algorithm framework */\nfn backtrack(state: &mut State, choices: &Vec<Choice>, res: &mut Vec<State>) {\n    // Check if it is a solution\n    if is_solution(state) {\n        // Record the solution\n        record_solution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for choice in choices {\n        // Pruning: check if the choice is valid\n        if is_valid(state, choice) {\n            // Attempt: make a choice and update the state\n            make_choice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            undo_choice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nvoid backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res, numRes);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < numChoices; i++) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, &choices[i])) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, &choices[i]);\n            backtrack(state, choices, numChoices, res, numRes);\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, &choices[i]);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nfun backtrack(state: State?, choices: List<Choice?>, res: List<State?>?) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res)\n        // Stop searching\n        return\n    }\n    // Traverse all choices\n    for (choice in choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice)\n            backtrack(state, choices, res)\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
### Backtracking algorithm framework ###\ndef backtrack(state, choices, res)\n    # Check if it is a solution\n    if is_solution?(state)\n        # Record the solution\n        record_solution(state, res)\n        return\n    end\n\n    # Traverse all choices\n    for choice in choices\n        # Pruning: check if the choice is valid\n        if is_valid?(state, choice)\n            # Attempt: make a choice and update the state\n            make_choice(state, choice)\n            backtrack(state, choices, res)\n            # Backtrack: undo the choice and restore to the previous state\n            undo_choice(state, choice)\n        end\n    end\nend\n

Next, we solve Example 3 based on the framework code. The state state is the node traversal path, the choices choices are the left and right child nodes of the current node, and the result res is a list of paths:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_iii_template.py
def is_solution(state: list[TreeNode]) -> bool:\n    \"\"\"Check if the current state is a solution\"\"\"\n    return state and state[-1].val == 7\n\ndef record_solution(state: list[TreeNode], res: list[list[TreeNode]]):\n    \"\"\"Record solution\"\"\"\n    res.append(list(state))\n\ndef is_valid(state: list[TreeNode], choice: TreeNode) -> bool:\n    \"\"\"Check if the choice is valid under the current state\"\"\"\n    return choice is not None and choice.val != 3\n\ndef make_choice(state: list[TreeNode], choice: TreeNode):\n    \"\"\"Update state\"\"\"\n    state.append(choice)\n\ndef undo_choice(state: list[TreeNode], choice: TreeNode):\n    \"\"\"Restore state\"\"\"\n    state.pop()\n\ndef backtrack(\n    state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]\n):\n    \"\"\"Backtracking algorithm: Example 3\"\"\"\n    # Check if it is a solution\n    if is_solution(state):\n        # Record solution\n        record_solution(state, res)\n    # Traverse all choices\n    for choice in choices:\n        # Pruning: check if the choice is valid\n        if is_valid(state, choice):\n            # Attempt: make choice, update state\n            make_choice(state, choice)\n            # Proceed to the next round of selection\n            backtrack(state, [choice.left, choice.right], res)\n            # Backtrack: undo choice, restore to previous state\n            undo_choice(state, choice)\n
preorder_traversal_iii_template.cpp
/* Check if the current state is a solution */\nbool isSolution(vector<TreeNode *> &state) {\n    return !state.empty() && state.back()->val == 7;\n}\n\n/* Record solution */\nvoid recordSolution(vector<TreeNode *> &state, vector<vector<TreeNode *>> &res) {\n    res.push_back(state);\n}\n\n/* Check if the choice is valid under the current state */\nbool isValid(vector<TreeNode *> &state, TreeNode *choice) {\n    return choice != nullptr && choice->val != 3;\n}\n\n/* Update state */\nvoid makeChoice(vector<TreeNode *> &state, TreeNode *choice) {\n    state.push_back(choice);\n}\n\n/* Restore state */\nvoid undoChoice(vector<TreeNode *> &state, TreeNode *choice) {\n    state.pop_back();\n}\n\n/* Backtracking algorithm: Example 3 */\nvoid backtrack(vector<TreeNode *> &state, vector<TreeNode *> &choices, vector<vector<TreeNode *>> &res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record solution\n        recordSolution(state, res);\n    }\n    // Traverse all choices\n    for (TreeNode *choice : choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice);\n            // Proceed to the next round of selection\n            vector<TreeNode *> nextChoices{choice->left, choice->right};\n            backtrack(state, nextChoices, res);\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
preorder_traversal_iii_template.java
/* Check if the current state is a solution */\nboolean isSolution(List<TreeNode> state) {\n    return !state.isEmpty() && state.get(state.size() - 1).val == 7;\n}\n\n/* Record solution */\nvoid recordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n    res.add(new ArrayList<>(state));\n}\n\n/* Check if the choice is valid under the current state */\nboolean isValid(List<TreeNode> state, TreeNode choice) {\n    return choice != null && choice.val != 3;\n}\n\n/* Update state */\nvoid makeChoice(List<TreeNode> state, TreeNode choice) {\n    state.add(choice);\n}\n\n/* Restore state */\nvoid undoChoice(List<TreeNode> state, TreeNode choice) {\n    state.remove(state.size() - 1);\n}\n\n/* Backtracking algorithm: Example 3 */\nvoid backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record solution\n        recordSolution(state, res);\n    }\n    // Traverse all choices\n    for (TreeNode choice : choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice);\n            // Proceed to the next round of selection\n            backtrack(state, Arrays.asList(choice.left, choice.right), res);\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
preorder_traversal_iii_template.cs
/* Check if the current state is a solution */\nbool IsSolution(List<TreeNode> state) {\n    return state.Count != 0 && state[^1].val == 7;\n}\n\n/* Record solution */\nvoid RecordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n    res.Add(new List<TreeNode>(state));\n}\n\n/* Check if the choice is valid under the current state */\nbool IsValid(List<TreeNode> state, TreeNode choice) {\n    return choice != null && choice.val != 3;\n}\n\n/* Update state */\nvoid MakeChoice(List<TreeNode> state, TreeNode choice) {\n    state.Add(choice);\n}\n\n/* Restore state */\nvoid UndoChoice(List<TreeNode> state, TreeNode choice) {\n    state.RemoveAt(state.Count - 1);\n}\n\n/* Backtracking algorithm: Example 3 */\nvoid Backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> res) {\n    // Check if it is a solution\n    if (IsSolution(state)) {\n        // Record solution\n        RecordSolution(state, res);\n    }\n    // Traverse all choices\n    foreach (TreeNode choice in choices) {\n        // Pruning: check if the choice is valid\n        if (IsValid(state, choice)) {\n            // Attempt: make choice, update state\n            MakeChoice(state, choice);\n            // Proceed to the next round of selection\n            Backtrack(state, [choice.left!, choice.right!], res);\n            // Backtrack: undo choice, restore to previous state\n            UndoChoice(state, choice);\n        }\n    }\n}\n
preorder_traversal_iii_template.go
/* Check if the current state is a solution */\nfunc isSolution(state *[]*TreeNode) bool {\n    return len(*state) != 0 && (*state)[len(*state)-1].Val == 7\n}\n\n/* Record solution */\nfunc recordSolution(state *[]*TreeNode, res *[][]*TreeNode) {\n    *res = append(*res, append([]*TreeNode{}, *state...))\n}\n\n/* Check if the choice is valid under the current state */\nfunc isValid(state *[]*TreeNode, choice *TreeNode) bool {\n    return choice != nil && choice.Val != 3\n}\n\n/* Update state */\nfunc makeChoice(state *[]*TreeNode, choice *TreeNode) {\n    *state = append(*state, choice)\n}\n\n/* Restore state */\nfunc undoChoice(state *[]*TreeNode, choice *TreeNode) {\n    *state = (*state)[:len(*state)-1]\n}\n\n/* Backtracking algorithm: Example 3 */\nfunc backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) {\n    // Check if it is a solution\n    if isSolution(state) {\n        // Record solution\n        recordSolution(state, res)\n    }\n    // Traverse all choices\n    for _, choice := range *choices {\n        // Pruning: check if the choice is valid\n        if isValid(state, choice) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice)\n            // Proceed to the next round of selection\n            temp := make([]*TreeNode, 0)\n            temp = append(temp, choice.Left, choice.Right)\n            backtrackIII(state, &temp, res)\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
preorder_traversal_iii_template.swift
/* Check if the current state is a solution */\nfunc isSolution(state: [TreeNode]) -> Bool {\n    !state.isEmpty && state.last!.val == 7\n}\n\n/* Record solution */\nfunc recordSolution(state: [TreeNode], res: inout [[TreeNode]]) {\n    res.append(state)\n}\n\n/* Check if the choice is valid under the current state */\nfunc isValid(state: [TreeNode], choice: TreeNode?) -> Bool {\n    choice != nil && choice!.val != 3\n}\n\n/* Update state */\nfunc makeChoice(state: inout [TreeNode], choice: TreeNode) {\n    state.append(choice)\n}\n\n/* Restore state */\nfunc undoChoice(state: inout [TreeNode], choice: TreeNode) {\n    state.removeLast()\n}\n\n/* Backtracking algorithm: Example 3 */\nfunc backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) {\n    // Check if it is a solution\n    if isSolution(state: state) {\n        recordSolution(state: state, res: &res)\n    }\n    // Traverse all choices\n    for choice in choices {\n        // Pruning: check if the choice is valid\n        if isValid(state: state, choice: choice) {\n            // Attempt: make choice, update state\n            makeChoice(state: &state, choice: choice)\n            // Proceed to the next round of selection\n            backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res)\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state: &state, choice: choice)\n        }\n    }\n}\n
preorder_traversal_iii_template.js
/* Check if the current state is a solution */\nfunction isSolution(state) {\n    return state && state[state.length - 1]?.val === 7;\n}\n\n/* Record solution */\nfunction recordSolution(state, res) {\n    res.push([...state]);\n}\n\n/* Check if the choice is valid under the current state */\nfunction isValid(state, choice) {\n    return choice !== null && choice.val !== 3;\n}\n\n/* Update state */\nfunction makeChoice(state, choice) {\n    state.push(choice);\n}\n\n/* Restore state */\nfunction undoChoice(state) {\n    state.pop();\n}\n\n/* Backtracking algorithm: Example 3 */\nfunction backtrack(state, choices, res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record solution\n        recordSolution(state, res);\n    }\n    // Traverse all choices\n    for (const choice of choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice);\n            // Proceed to the next round of selection\n            backtrack(state, [choice.left, choice.right], res);\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state);\n        }\n    }\n}\n
preorder_traversal_iii_template.ts
/* Check if the current state is a solution */\nfunction isSolution(state: TreeNode[]): boolean {\n    return state && state[state.length - 1]?.val === 7;\n}\n\n/* Record solution */\nfunction recordSolution(state: TreeNode[], res: TreeNode[][]): void {\n    res.push([...state]);\n}\n\n/* Check if the choice is valid under the current state */\nfunction isValid(state: TreeNode[], choice: TreeNode): boolean {\n    return choice !== null && choice.val !== 3;\n}\n\n/* Update state */\nfunction makeChoice(state: TreeNode[], choice: TreeNode): void {\n    state.push(choice);\n}\n\n/* Restore state */\nfunction undoChoice(state: TreeNode[]): void {\n    state.pop();\n}\n\n/* Backtracking algorithm: Example 3 */\nfunction backtrack(\n    state: TreeNode[],\n    choices: TreeNode[],\n    res: TreeNode[][]\n): void {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record solution\n        recordSolution(state, res);\n    }\n    // Traverse all choices\n    for (const choice of choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice);\n            // Proceed to the next round of selection\n            backtrack(state, [choice.left, choice.right], res);\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state);\n        }\n    }\n}\n
preorder_traversal_iii_template.dart
/* Check if the current state is a solution */\nbool isSolution(List<TreeNode> state) {\n  return state.isNotEmpty && state.last.val == 7;\n}\n\n/* Record solution */\nvoid recordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n  res.add(List.from(state));\n}\n\n/* Check if the choice is valid under the current state */\nbool isValid(List<TreeNode> state, TreeNode? choice) {\n  return choice != null && choice.val != 3;\n}\n\n/* Update state */\nvoid makeChoice(List<TreeNode> state, TreeNode? choice) {\n  state.add(choice!);\n}\n\n/* Restore state */\nvoid undoChoice(List<TreeNode> state, TreeNode? choice) {\n  state.removeLast();\n}\n\n/* Backtracking algorithm: Example 3 */\nvoid backtrack(\n  List<TreeNode> state,\n  List<TreeNode?> choices,\n  List<List<TreeNode>> res,\n) {\n  // Check if it is a solution\n  if (isSolution(state)) {\n    // Record solution\n    recordSolution(state, res);\n  }\n  // Traverse all choices\n  for (TreeNode? choice in choices) {\n    // Pruning: check if the choice is valid\n    if (isValid(state, choice)) {\n      // Attempt: make choice, update state\n      makeChoice(state, choice);\n      // Proceed to the next round of selection\n      backtrack(state, [choice!.left, choice.right], res);\n      // Backtrack: undo choice, restore to previous state\n      undoChoice(state, choice);\n    }\n  }\n}\n
preorder_traversal_iii_template.rs
/* Check if the current state is a solution */\nfn is_solution(state: &mut Vec<Rc<RefCell<TreeNode>>>) -> bool {\n    return !state.is_empty() && state.last().unwrap().borrow().val == 7;\n}\n\n/* Record solution */\nfn record_solution(\n    state: &mut Vec<Rc<RefCell<TreeNode>>>,\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n) {\n    res.push(state.clone());\n}\n\n/* Check if the choice is valid under the current state */\nfn is_valid(_: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Option<&Rc<RefCell<TreeNode>>>) -> bool {\n    return choice.is_some() && choice.unwrap().borrow().val != 3;\n}\n\n/* Update state */\nfn make_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Rc<RefCell<TreeNode>>) {\n    state.push(choice);\n}\n\n/* Restore state */\nfn undo_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, _: Rc<RefCell<TreeNode>>) {\n    state.pop();\n}\n\n/* Backtracking algorithm: Example 3 */\nfn backtrack(\n    state: &mut Vec<Rc<RefCell<TreeNode>>>,\n    choices: &Vec<Option<&Rc<RefCell<TreeNode>>>>,\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n) {\n    // Check if it is a solution\n    if is_solution(state) {\n        // Record solution\n        record_solution(state, res);\n    }\n    // Traverse all choices\n    for &choice in choices.iter() {\n        // Pruning: check if the choice is valid\n        if is_valid(state, choice) {\n            // Attempt: make choice, update state\n            make_choice(state, choice.unwrap().clone());\n            // Proceed to the next round of selection\n            backtrack(\n                state,\n                &vec![\n                    choice.unwrap().borrow().left.as_ref(),\n                    choice.unwrap().borrow().right.as_ref(),\n                ],\n                res,\n            );\n            // Backtrack: undo choice, restore to previous state\n            undo_choice(state, choice.unwrap().clone());\n        }\n    }\n}\n
preorder_traversal_iii_template.c
/* Check if the current state is a solution */\nbool isSolution(void) {\n    return pathSize > 0 && path[pathSize - 1]->val == 7;\n}\n\n/* Record solution */\nvoid recordSolution(void) {\n    for (int i = 0; i < pathSize; i++) {\n        res[resSize][i] = path[i];\n    }\n    resSize++;\n}\n\n/* Check if the choice is valid under the current state */\nbool isValid(TreeNode *choice) {\n    return choice != NULL && choice->val != 3;\n}\n\n/* Update state */\nvoid makeChoice(TreeNode *choice) {\n    path[pathSize++] = choice;\n}\n\n/* Restore state */\nvoid undoChoice(void) {\n    pathSize--;\n}\n\n/* Backtracking algorithm: Example 3 */\nvoid backtrack(TreeNode *choices[2]) {\n    // Check if it is a solution\n    if (isSolution()) {\n        // Record solution\n        recordSolution();\n    }\n    // Traverse all choices\n    for (int i = 0; i < 2; i++) {\n        TreeNode *choice = choices[i];\n        // Pruning: check if the choice is valid\n        if (isValid(choice)) {\n            // Attempt: make choice, update state\n            makeChoice(choice);\n            // Proceed to the next round of selection\n            TreeNode *nextChoices[2] = {choice->left, choice->right};\n            backtrack(nextChoices);\n            // Backtrack: undo choice, restore to previous state\n            undoChoice();\n        }\n    }\n}\n
preorder_traversal_iii_template.kt
/* Check if the current state is a solution */\nfun isSolution(state: MutableList<TreeNode?>): Boolean {\n    return state.isNotEmpty() && state[state.size - 1]?._val == 7\n}\n\n/* Record solution */\nfun recordSolution(state: MutableList<TreeNode?>?, res: MutableList<MutableList<TreeNode?>?>) {\n    res.add(state!!.toMutableList())\n}\n\n/* Check if the choice is valid under the current state */\nfun isValid(state: MutableList<TreeNode?>?, choice: TreeNode?): Boolean {\n    return choice != null && choice._val != 3\n}\n\n/* Update state */\nfun makeChoice(state: MutableList<TreeNode?>, choice: TreeNode?) {\n    state.add(choice)\n}\n\n/* Restore state */\nfun undoChoice(state: MutableList<TreeNode?>, choice: TreeNode?) {\n    state.removeLast()\n}\n\n/* Backtracking algorithm: Example 3 */\nfun backtrack(\n    state: MutableList<TreeNode?>,\n    choices: MutableList<TreeNode?>,\n    res: MutableList<MutableList<TreeNode?>?>\n) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record solution\n        recordSolution(state, res)\n    }\n    // Traverse all choices\n    for (choice in choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice)\n            // Proceed to the next round of selection\n            backtrack(state, mutableListOf(choice!!.left, choice.right), res)\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
preorder_traversal_iii_template.rb
### Check if current state is solution ###\ndef is_solution?(state)\n  !state.empty? && state.last.val == 7\nend\n\n### Record solution ###\ndef record_solution(state, res)\n  res << state.dup\nend\n\n### Check if choice is valid in current state ###\ndef is_valid?(state, choice)\n  choice && choice.val != 3\nend\n\n### Update state ###\ndef make_choice(state, choice)\n  state << choice\nend\n\n### Restore state ###\ndef undo_choice(state, choice)\n  state.pop\nend\n\n### Backtracking: example 3 ###\ndef backtrack(state, choices, res)\n  # Check if it is a solution\n  record_solution(state, res) if is_solution?(state)\n\n  # Traverse all choices\n  for choice in choices\n    # Pruning: check if the choice is valid\n    if is_valid?(state, choice)\n      # Attempt: make choice, update state\n      make_choice(state, choice)\n      # Proceed to the next round of selection\n      backtrack(state, [choice.left, choice.right], res)\n      # Backtrack: undo choice, restore to previous state\n      undo_choice(state, choice)\n    end\n  end\nend\n

As per the problem statement, we should continue searching after finding a node with value \\(7\\). Therefore, we need to remove the return statement after recording the solution. The following figure compares the search process with and without the return statement.

Figure 13-4   Comparison of search process with and without return statement

Compared to code based on preorder traversal, code based on the backtracking algorithm framework appears more verbose, but has better generality. In fact, many backtracking problems can be solved within this framework. We only need to define state and choices for the specific problem and implement each method in the framework.

","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1314-common-terminology","level":2,"title":"13.1.4   Common Terminology","text":"

To analyze algorithmic problems more clearly, we summarize the meanings of common terminology used in backtracking algorithms and provide corresponding examples from Example 3, as shown in the following table.

Table 13-1   Common Backtracking Algorithm Terminology

Term Definition Example 3 Solution (solution) A solution is an answer that satisfies the specific conditions of a problem; there may be one or more solutions All paths from root to nodes with value \\(7\\) that satisfy the constraint Constraint (constraint) A constraint is a condition in the problem that limits the feasibility of solutions, typically used for pruning Paths do not contain nodes with value \\(3\\) State (state) State represents the situation of a problem at a certain moment, including the choices already made The currently visited node path, i.e., the path list of nodes Attempt (attempt) An attempt is the process of exploring the solution space according to available choices, including making choices, updating state, and checking if it is a solution Recursively visit left (right) child nodes, add nodes to path, check if node value is \\(7\\) Backtrack (backtracking) Backtracking refers to undoing previous choices and returning to a previous state when encountering a state that does not satisfy constraints Stop searching when passing over leaf nodes, ending node visits, or encountering nodes with value \\(3\\); function returns Pruning (pruning) Pruning is a method of avoiding meaningless search paths according to problem characteristics and constraints, which can improve search efficiency When encountering a node with value \\(3\\), do not continue searching

Tip

The concepts of problem, solution, state, etc. are universal and are involved in divide-and-conquer, backtracking, dynamic programming, greedy and other algorithms.

","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1315-advantages-and-limitations","level":2,"title":"13.1.5   Advantages and Limitations","text":"

The backtracking algorithm is essentially a depth-first search algorithm that tries all possible solutions until it finds one that satisfies the conditions. The advantage of this approach is that it can find all possible solutions, and with reasonable pruning operations, it achieves high efficiency.

However, when dealing with large-scale or complex problems, the running efficiency of the backtracking algorithm may be unacceptable.

  • Time: The backtracking algorithm usually needs to traverse all possibilities in the solution space, and the time complexity can reach exponential or factorial order.
  • Space: During recursive calls, the current state needs to be saved (such as paths, auxiliary variables used for pruning, etc.), and when the depth is large, the space requirement can become very large.

Nevertheless, the backtracking algorithm is still the best solution for certain search problems and constraint satisfaction problems. For these problems, since we cannot predict which choices will generate valid solutions, we must traverse all possible choices. In this case, the key is how to optimize efficiency. There are two common efficiency optimization methods.

  • Pruning: Avoid searching paths that are guaranteed not to produce solutions, thereby saving time and space.
  • Heuristic search: Introduce certain strategies or estimation values during the search process to prioritize searching paths that are most likely to produce valid solutions.
","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1316-typical-backtracking-examples","level":2,"title":"13.1.6   Typical Backtracking Examples","text":"

The backtracking algorithm can be used to solve many search problems, constraint satisfaction problems, and combinatorial optimization problems.

Search problems: The goal of these problems is to find solutions that satisfy specific conditions.

  • Permutation problem: Given a set, find all possible permutations and combinations.
  • Subset sum problem: Given a set and a target sum, find all subsets in the set whose elements sum to the target.
  • Tower of Hanoi: Given three pegs and a series of disks of different sizes, move all disks from one peg to another, moving only one disk at a time, and never placing a larger disk on a smaller disk.

Constraint satisfaction problems: The goal of these problems is to find solutions that satisfy all constraints.

  • N-Queens: Place \\(n\\) queens on an \\(n \\times n\\) chessboard such that they do not attack each other.
  • Sudoku: Fill numbers \\(1\\) to \\(9\\) in a \\(9 \\times 9\\) grid such that each row, column, and \\(3 \\times 3\\) subgrid contains no repeated digits.
  • Graph coloring: Given an undirected graph, color each vertex with the minimum number of colors such that adjacent vertices have different colors.

Combinatorial optimization problems: The goal of these problems is to find an optimal solution that satisfies certain conditions in a combinatorial space.

  • 0-1 Knapsack: Given a set of items and a knapsack, each item has a value and weight. Under the knapsack capacity constraint, select items to maximize total value.
  • Traveling Salesman Problem: Starting from a point in a graph, visit all other points exactly once and return to the starting point, finding the shortest path.
  • Maximum Clique: Given an undirected graph, find the largest complete subgraph, i.e., a subgraph where any two vertices are connected by an edge.

Note that for many combinatorial optimization problems, backtracking is not the optimal solution.

  • The 0-1 Knapsack problem is usually solved using dynamic programming to achieve higher time efficiency.
  • The Traveling Salesman Problem is a famous NP-Hard problem; common solutions include genetic algorithms and ant colony algorithms.
  • The Maximum Clique problem is a classical problem in graph theory and can be solved using heuristic algorithms such as greedy algorithms.
","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/","level":1,"title":"13.4   N-Queens Problem","text":"

Question

According to the rules of chess, a queen can attack pieces that share the same row, column, or diagonal line. Given \\(n\\) queens and an \\(n \\times n\\) chessboard, find a placement scheme such that no two queens can attack each other.

As shown in Figure 13-15, when \\(n = 4\\), there are two solutions that can be found. From the perspective of the backtracking algorithm, an \\(n \\times n\\) chessboard has \\(n^2\\) squares, which provide all the choices choices. During the process of placing queens one by one, the chessboard state changes continuously, and the chessboard at each moment represents the state state.

Figure 13-15   Solution to the 4-queens problem

Figure 13-16 illustrates the three constraints of this problem: multiple queens cannot be in the same row, the same column, or on the same diagonal. It is worth noting that diagonals are divided into two types: the main diagonal \\ and the anti-diagonal /.

Figure 13-16   Constraints of the n-queens problem

","path":["Chapter 13. Backtracking","13.4   N-Queens Problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#1-row-by-row-placement-strategy","level":3,"title":"1.   Row-By-Row Placement Strategy","text":"

Since both the number of queens and the number of rows on the chessboard are \\(n\\), we can easily derive a conclusion: each row of the chessboard allows and only allows exactly one queen to be placed.

This means we can adopt a row-by-row placement strategy: starting from the first row, place one queen in each row until the last row is completed.

Figure 13-17 shows the row-by-row placement process for the 4-queens problem. Due to space limitations, the figure only expands one search branch of the first row, and all schemes that do not satisfy the column constraint and diagonal constraints are pruned.

Figure 13-17   Row-by-row placement strategy

Essentially, the row-by-row placement strategy serves a pruning function, as it avoids all search branches where multiple queens appear in the same row.

","path":["Chapter 13. Backtracking","13.4   N-Queens Problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#2-column-and-diagonal-pruning","level":3,"title":"2.   Column and Diagonal Pruning","text":"

To satisfy the column constraint, we can use a boolean array cols of length \\(n\\) to record whether each column has a queen. Before each placement decision, we use cols to prune columns that already have queens, and dynamically update the state of cols during backtracking.

Tip

Please note that the origin of the matrix is located in the upper-left corner, where the row index increases from top to bottom, and the column index increases from left to right.

So how do we handle diagonal constraints? Consider a square on the chessboard with row and column indices \\((row, col)\\). If we select a specific main diagonal in the matrix, we find that all squares on that diagonal have the same difference between their row and column indices, meaning that \\(row - col\\) is a constant value for all squares on the main diagonal.

In other words, if two squares satisfy \\(row_1 - col_1 = row_2 - col_2\\), they must be on the same main diagonal. Using this pattern, we can use the array diags1 shown in Figure 13-18 to record whether there is a queen on each main diagonal.

Similarly, for all squares on an anti-diagonal, the sum \\(row + col\\) is a constant value. We can likewise use the array diags2 to handle anti-diagonal constraints.

Figure 13-18   Handling column and diagonal constraints

","path":["Chapter 13. Backtracking","13.4   N-Queens Problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#3-code-implementation","level":3,"title":"3.   Code Implementation","text":"

Please note that in an \\(n\\)-dimensional square matrix, the range of \\(row - col\\) is \\([-n + 1, n - 1]\\), and the range of \\(row + col\\) is \\([0, 2n - 2]\\). Therefore, the number of both main diagonals and anti-diagonals is \\(2n - 1\\), meaning the length of both arrays diags1 and diags2 is \\(2n - 1\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby n_queens.py
def backtrack(\n    row: int,\n    n: int,\n    state: list[list[str]],\n    res: list[list[list[str]]],\n    cols: list[bool],\n    diags1: list[bool],\n    diags2: list[bool],\n):\n    \"\"\"Backtracking algorithm: N queens\"\"\"\n    # When all rows are placed, record the solution\n    if row == n:\n        res.append([list(row) for row in state])\n        return\n    # Traverse all columns\n    for col in range(n):\n        # Calculate the main diagonal and anti-diagonal corresponding to this cell\n        diag1 = row - col + n - 1\n        diag2 = row + col\n        # Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if not cols[col] and not diags1[diag1] and not diags2[diag2]:\n            # Attempt: place the queen in this cell\n            state[row][col] = \"Q\"\n            cols[col] = diags1[diag1] = diags2[diag2] = True\n            # Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2)\n            # Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\"\n            cols[col] = diags1[diag1] = diags2[diag2] = False\n\ndef n_queens(n: int) -> list[list[list[str]]]:\n    \"\"\"Solve N queens\"\"\"\n    # Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    state = [[\"#\" for _ in range(n)] for _ in range(n)]\n    cols = [False] * n  # Record whether there is a queen in the column\n    diags1 = [False] * (2 * n - 1)  # Record whether there is a queen on the main diagonal\n    diags2 = [False] * (2 * n - 1)  # Record whether there is a queen on the anti-diagonal\n    res = []\n    backtrack(0, n, state, res, cols, diags1, diags2)\n\n    return res\n
n_queens.cpp
/* Backtracking algorithm: N queens */\nvoid backtrack(int row, int n, vector<vector<string>> &state, vector<vector<vector<string>>> &res, vector<bool> &cols,\n               vector<bool> &diags1, vector<bool> &diags2) {\n    // When all rows are placed, record the solution\n    if (row == n) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = \"Q\";\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\";\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nvector<vector<vector<string>>> nQueens(int n) {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    vector<vector<string>> state(n, vector<string>(n, \"#\"));\n    vector<bool> cols(n, false);           // Record whether there is a queen in the column\n    vector<bool> diags1(2 * n - 1, false); // Record whether there is a queen on the main diagonal\n    vector<bool> diags2(2 * n - 1, false); // Record whether there is a queen on the anti-diagonal\n    vector<vector<vector<string>>> res;\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
n_queens.java
/* Backtracking algorithm: N queens */\nvoid backtrack(int row, int n, List<List<String>> state, List<List<List<String>>> res,\n        boolean[] cols, boolean[] diags1, boolean[] diags2) {\n    // When all rows are placed, record the solution\n    if (row == n) {\n        List<List<String>> copyState = new ArrayList<>();\n        for (List<String> sRow : state) {\n            copyState.add(new ArrayList<>(sRow));\n        }\n        res.add(copyState);\n        return;\n    }\n    // Traverse all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state.get(row).set(col, \"Q\");\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state.get(row).set(col, \"#\");\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nList<List<List<String>>> nQueens(int n) {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    List<List<String>> state = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        List<String> row = new ArrayList<>();\n        for (int j = 0; j < n; j++) {\n            row.add(\"#\");\n        }\n        state.add(row);\n    }\n    boolean[] cols = new boolean[n]; // Record whether there is a queen in the column\n    boolean[] diags1 = new boolean[2 * n - 1]; // Record whether there is a queen on the main diagonal\n    boolean[] diags2 = new boolean[2 * n - 1]; // Record whether there is a queen on the anti-diagonal\n    List<List<List<String>>> res = new ArrayList<>();\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
n_queens.cs
/* Backtracking algorithm: N queens */\nvoid Backtrack(int row, int n, List<List<string>> state, List<List<List<string>>> res,\n        bool[] cols, bool[] diags1, bool[] diags2) {\n    // When all rows are placed, record the solution\n    if (row == n) {\n        List<List<string>> copyState = [];\n        foreach (List<string> sRow in state) {\n            copyState.Add(new List<string>(sRow));\n        }\n        res.Add(copyState);\n        return;\n    }\n    // Traverse all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = \"Q\";\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            Backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\";\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nList<List<List<string>>> NQueens(int n) {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    List<List<string>> state = [];\n    for (int i = 0; i < n; i++) {\n        List<string> row = [];\n        for (int j = 0; j < n; j++) {\n            row.Add(\"#\");\n        }\n        state.Add(row);\n    }\n    bool[] cols = new bool[n]; // Record whether there is a queen in the column\n    bool[] diags1 = new bool[2 * n - 1]; // Record whether there is a queen on the main diagonal\n    bool[] diags2 = new bool[2 * n - 1]; // Record whether there is a queen on the anti-diagonal\n    List<List<List<string>>> res = [];\n\n    Backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
n_queens.go
/* Backtracking algorithm: N queens */\nfunc backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) {\n    // When all rows are placed, record the solution\n    if row == n {\n        newState := make([][]string, len(*state))\n        for i, _ := range newState {\n            newState[i] = make([]string, len((*state)[0]))\n            copy(newState[i], (*state)[i])\n\n        }\n        *res = append(*res, newState)\n        return\n    }\n    // Traverse all columns\n    for col := 0; col < n; col++ {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        diag1 := row - col + n - 1\n        diag2 := row + col\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] {\n            // Attempt: place the queen in this cell\n            (*state)[row][col] = \"Q\"\n            (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true\n            // Place the next row\n            backtrack(row+1, n, state, res, cols, diags1, diags2)\n            // Backtrack: restore this cell to an empty cell\n            (*state)[row][col] = \"#\"\n            (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false\n        }\n    }\n}\n\n/* Solve N queens */\nfunc nQueens(n int) [][][]string {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    state := make([][]string, n)\n    for i := 0; i < n; i++ {\n        row := make([]string, n)\n        for i := 0; i < n; i++ {\n            row[i] = \"#\"\n        }\n        state[i] = row\n    }\n    // Record whether there is a queen in the column\n    cols := make([]bool, n)\n    diags1 := make([]bool, 2*n-1)\n    diags2 := make([]bool, 2*n-1)\n    res := make([][][]string, 0)\n    backtrack(0, n, &state, &res, &cols, &diags1, &diags2)\n    return res\n}\n
n_queens.swift
/* Backtracking algorithm: N queens */\nfunc backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) {\n    // When all rows are placed, record the solution\n    if row == n {\n        res.append(state)\n        return\n    }\n    // Traverse all columns\n    for col in 0 ..< n {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        let diag1 = row - col + n - 1\n        let diag2 = row + col\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if !cols[col] && !diags1[diag1] && !diags2[diag2] {\n            // Attempt: place the queen in this cell\n            state[row][col] = \"Q\"\n            cols[col] = true\n            diags1[diag1] = true\n            diags2[diag2] = true\n            // Place the next row\n            backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2)\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\"\n            cols[col] = false\n            diags1[diag1] = false\n            diags2[diag2] = false\n        }\n    }\n}\n\n/* Solve N queens */\nfunc nQueens(n: Int) -> [[[String]]] {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    var state = Array(repeating: Array(repeating: \"#\", count: n), count: n)\n    var cols = Array(repeating: false, count: n) // Record whether there is a queen in the column\n    var diags1 = Array(repeating: false, count: 2 * n - 1) // Record whether there is a queen on the main diagonal\n    var diags2 = Array(repeating: false, count: 2 * n - 1) // Record whether there is a queen on the anti-diagonal\n    var res: [[[String]]] = []\n\n    backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2)\n\n    return res\n}\n
n_queens.js
/* Backtracking algorithm: N queens */\nfunction backtrack(row, n, state, res, cols, diags1, diags2) {\n    // When all rows are placed, record the solution\n    if (row === n) {\n        res.push(state.map((row) => row.slice()));\n        return;\n    }\n    // Traverse all columns\n    for (let col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        const diag1 = row - col + n - 1;\n        const diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nfunction nQueens(n) {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    const state = Array.from({ length: n }, () => Array(n).fill('#'));\n    const cols = Array(n).fill(false); // Record whether there is a queen in the column\n    const diags1 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the main diagonal\n    const diags2 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the anti-diagonal\n    const res = [];\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n    return res;\n}\n
n_queens.ts
/* Backtracking algorithm: N queens */\nfunction backtrack(\n    row: number,\n    n: number,\n    state: string[][],\n    res: string[][][],\n    cols: boolean[],\n    diags1: boolean[],\n    diags2: boolean[]\n): void {\n    // When all rows are placed, record the solution\n    if (row === n) {\n        res.push(state.map((row) => row.slice()));\n        return;\n    }\n    // Traverse all columns\n    for (let col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        const diag1 = row - col + n - 1;\n        const diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nfunction nQueens(n: number): string[][][] {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    const state = Array.from({ length: n }, () => Array(n).fill('#'));\n    const cols = Array(n).fill(false); // Record whether there is a queen in the column\n    const diags1 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the main diagonal\n    const diags2 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the anti-diagonal\n    const res: string[][][] = [];\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n    return res;\n}\n
n_queens.dart
/* Backtracking algorithm: N queens */\nvoid backtrack(\n  int row,\n  int n,\n  List<List<String>> state,\n  List<List<List<String>>> res,\n  List<bool> cols,\n  List<bool> diags1,\n  List<bool> diags2,\n) {\n  // When all rows are placed, record the solution\n  if (row == n) {\n    List<List<String>> copyState = [];\n    for (List<String> sRow in state) {\n      copyState.add(List.from(sRow));\n    }\n    res.add(copyState);\n    return;\n  }\n  // Traverse all columns\n  for (int col = 0; col < n; col++) {\n    // Calculate the main diagonal and anti-diagonal corresponding to this cell\n    int diag1 = row - col + n - 1;\n    int diag2 = row + col;\n    // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n    if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n      // Attempt: place the queen in this cell\n      state[row][col] = \"Q\";\n      cols[col] = true;\n      diags1[diag1] = true;\n      diags2[diag2] = true;\n      // Place the next row\n      backtrack(row + 1, n, state, res, cols, diags1, diags2);\n      // Backtrack: restore this cell to an empty cell\n      state[row][col] = \"#\";\n      cols[col] = false;\n      diags1[diag1] = false;\n      diags2[diag2] = false;\n    }\n  }\n}\n\n/* Solve N queens */\nList<List<List<String>>> nQueens(int n) {\n  // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n  List<List<String>> state = List.generate(n, (index) => List.filled(n, \"#\"));\n  List<bool> cols = List.filled(n, false); // Record whether there is a queen in the column\n  List<bool> diags1 = List.filled(2 * n - 1, false); // Record whether there is a queen on the main diagonal\n  List<bool> diags2 = List.filled(2 * n - 1, false); // Record whether there is a queen on the anti-diagonal\n  List<List<List<String>>> res = [];\n\n  backtrack(0, n, state, res, cols, diags1, diags2);\n\n  return res;\n}\n
n_queens.rs
/* Backtracking algorithm: N queens */\nfn backtrack(\n    row: usize,\n    n: usize,\n    state: &mut Vec<Vec<String>>,\n    res: &mut Vec<Vec<Vec<String>>>,\n    cols: &mut [bool],\n    diags1: &mut [bool],\n    diags2: &mut [bool],\n) {\n    // When all rows are placed, record the solution\n    if row == n {\n        res.push(state.clone());\n        return;\n    }\n    // Traverse all columns\n    for col in 0..n {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        let diag1 = row + n - 1 - col;\n        let diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if !cols[col] && !diags1[diag1] && !diags2[diag2] {\n            // Attempt: place the queen in this cell\n            state[row][col] = \"Q\".into();\n            (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true);\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\".into();\n            (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false);\n        }\n    }\n}\n\n/* Solve N queens */\nfn n_queens(n: usize) -> Vec<Vec<Vec<String>>> {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    let mut state: Vec<Vec<String>> = vec![vec![\"#\".to_string(); n]; n];\n    let mut cols = vec![false; n]; // Record whether there is a queen in the column\n    let mut diags1 = vec![false; 2 * n - 1]; // Record whether there is a queen on the main diagonal\n    let mut diags2 = vec![false; 2 * n - 1]; // Record whether there is a queen on the anti-diagonal\n    let mut res: Vec<Vec<Vec<String>>> = Vec::new();\n\n    backtrack(\n        0,\n        n,\n        &mut state,\n        &mut res,\n        &mut cols,\n        &mut diags1,\n        &mut diags2,\n    );\n\n    res\n}\n
n_queens.c
/* Backtracking algorithm: N queens */\nvoid backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE],\n               bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) {\n    // When all rows are placed, record the solution\n    if (row == n) {\n        res[*resSize] = (char **)malloc(sizeof(char *) * n);\n        for (int i = 0; i < n; ++i) {\n            res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1));\n            strcpy(res[*resSize][i], state[i]);\n        }\n        (*resSize)++;\n        return;\n    }\n    // Traverse all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nchar ***nQueens(int n, int *returnSize) {\n    char state[MAX_SIZE][MAX_SIZE];\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    for (int i = 0; i < n; ++i) {\n        for (int j = 0; j < n; ++j) {\n            state[i][j] = '#';\n        }\n        state[i][n] = '\\0';\n    }\n    bool cols[MAX_SIZE] = {false};           // Record whether there is a queen in the column\n    bool diags1[2 * MAX_SIZE - 1] = {false}; // Record whether there is a queen on the main diagonal\n    bool diags2[2 * MAX_SIZE - 1] = {false}; // Record whether there is a queen on the anti-diagonal\n\n    char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE);\n    *returnSize = 0;\n    backtrack(0, n, state, res, returnSize, cols, diags1, diags2);\n    return res;\n}\n
n_queens.kt
/* Backtracking algorithm: N queens */\nfun backtrack(\n    row: Int,\n    n: Int,\n    state: MutableList<MutableList<String>>,\n    res: MutableList<MutableList<MutableList<String>>?>,\n    cols: BooleanArray,\n    diags1: BooleanArray,\n    diags2: BooleanArray\n) {\n    // When all rows are placed, record the solution\n    if (row == n) {\n        val copyState = mutableListOf<MutableList<String>>()\n        for (sRow in state) {\n            copyState.add(sRow.toMutableList())\n        }\n        res.add(copyState)\n        return\n    }\n    // Traverse all columns\n    for (col in 0..<n) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        val diag1 = row - col + n - 1\n        val diag2 = row + col\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = \"Q\"\n            diags2[diag2] = true\n            diags1[diag1] = diags2[diag2]\n            cols[col] = diags1[diag1]\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2)\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\"\n            diags2[diag2] = false\n            diags1[diag1] = diags2[diag2]\n            cols[col] = diags1[diag1]\n        }\n    }\n}\n\n/* Solve N queens */\nfun nQueens(n: Int): MutableList<MutableList<MutableList<String>>?> {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    val state = mutableListOf<MutableList<String>>()\n    for (i in 0..<n) {\n        val row = mutableListOf<String>()\n        for (j in 0..<n) {\n            row.add(\"#\")\n        }\n        state.add(row)\n    }\n    val cols = BooleanArray(n) // Record whether there is a queen in the column\n    val diags1 = BooleanArray(2 * n - 1) // Record whether there is a queen on the main diagonal\n    val diags2 = BooleanArray(2 * n - 1) // Record whether there is a queen on the anti-diagonal\n    val res = mutableListOf<MutableList<MutableList<String>>?>()\n\n    backtrack(0, n, state, res, cols, diags1, diags2)\n\n    return res\n}\n
n_queens.rb
### Backtracking: n queens ###\ndef backtrack(row, n, state, res, cols, diags1, diags2)\n  # When all rows are placed, record the solution\n  if row == n\n    res << state.map { |row| row.dup }\n    return\n  end\n\n  # Traverse all columns\n  for col in 0...n\n    # Calculate the main diagonal and anti-diagonal corresponding to this cell\n    diag1 = row - col + n - 1\n    diag2 = row + col\n    # Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n    if !cols[col] && !diags1[diag1] && !diags2[diag2]\n      # Attempt: place the queen in this cell\n      state[row][col] = \"Q\"\n      cols[col] = diags1[diag1] = diags2[diag2] = true\n      # Place the next row\n      backtrack(row + 1, n, state, res, cols, diags1, diags2)\n      # Backtrack: restore this cell to an empty cell\n      state[row][col] = \"#\"\n      cols[col] = diags1[diag1] = diags2[diag2] = false\n    end\n  end\nend\n\n### Solve n queens ###\ndef n_queens(n)\n  # Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n  state = Array.new(n) { Array.new(n, \"#\") }\n  cols = Array.new(n, false) # Record whether there is a queen in the column\n  diags1 = Array.new(2 * n - 1, false) # Record whether there is a queen on the main diagonal\n  diags2 = Array.new(2 * n - 1, false) # Record whether there is a queen on the anti-diagonal\n  res = []\n  backtrack(0, n, state, res, cols, diags1, diags2)\n\n  res\nend\n

Placing \\(n\\) queens row by row, considering the column constraint, from the first row to the last row there are \\(n\\), \\(n-1\\), \\(\\dots\\), \\(2\\), \\(1\\) choices, using \\(O(n!)\\) time. When recording a solution, it is necessary to copy the matrix state and add it to res, and the copy operation uses \\(O(n^2)\\) time. Therefore, the overall time complexity is \\(O(n! \\cdot n^2)\\). In practice, pruning based on diagonal constraints can also significantly reduce the search space, so the search efficiency is often better than the time complexity mentioned above.

The array state uses \\(O(n^2)\\) space, and the arrays cols, diags1, and diags2 each use \\(O(n)\\) space. The maximum recursion depth is \\(n\\), using \\(O(n)\\) stack frame space. Therefore, the space complexity is \\(O(n^2)\\).

","path":["Chapter 13. Backtracking","13.4   N-Queens Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/","level":1,"title":"13.2   Permutations Problem","text":"

The permutations problem is a classic application of backtracking algorithms. It is defined as finding all possible arrangements of elements in a given collection (such as an array or string).

Table 13-2 shows several example datasets, including input arrays and their corresponding permutations.

Table 13-2   Permutations Examples

Input Array All Permutations \\([1]\\) \\([1]\\) \\([1, 2]\\) \\([1, 2], [2, 1]\\) \\([1, 2, 3]\\) \\([1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]\\)","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1321-case-with-distinct-elements","level":2,"title":"13.2.1   Case with Distinct Elements","text":"

Question

Given an integer array with no duplicate elements, return all possible permutations.

From the perspective of backtracking algorithms, we can imagine the process of generating permutations as the result of a series of choices. Suppose the input array is \\([1, 2, 3]\\). If we first choose \\(1\\), then choose \\(3\\), and finally choose \\(2\\), we obtain the permutation \\([1, 3, 2]\\). Backtracking means undoing a choice and then trying other choices.

From the perspective of backtracking code, the candidate set choices consists of all elements in the input array, and the state state is the elements that have been chosen so far. Note that each element can only be chosen once, therefore all elements in state should be unique.

As shown in Figure 13-5, we can unfold the search process into a recursion tree, where each node in the tree represents the current state state. Starting from the root node, after three rounds of choices, we reach a leaf node, and each leaf node corresponds to a permutation.

Figure 13-5   Recursion tree of permutations

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1-pruning-duplicate-choices","level":3,"title":"1.   Pruning Duplicate Choices","text":"

To ensure that each element is chosen only once, we consider introducing a boolean array selected, where selected[i] indicates whether choices[i] has been chosen. We implement the following pruning operation based on it.

  • After making a choice choice[i], we set selected[i] to \\(\\text{True}\\), indicating that it has been chosen.
  • When traversing the candidate list choices, we skip all nodes that have been chosen, which is pruning.

As shown in Figure 13-6, suppose we choose \\(1\\) in the first round, \\(3\\) in the second round, and \\(2\\) in the third round. Then we need to prune the branch of element \\(1\\) in the second round and prune the branches of elements \\(1\\) and \\(3\\) in the third round.

Figure 13-6   Pruning example of permutations

Observing the above figure, we find that this pruning operation reduces the search space size from \\(O(n^n)\\) to \\(O(n!)\\).

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"

After understanding the above information, we can fill in the blanks in the template code. To shorten the overall code, we do not implement each function in the template separately, but instead unfold them in the backtrack() function:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby permutations_i.py
def backtrack(\n    state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]\n):\n    \"\"\"Backtracking algorithm: Permutations I\"\"\"\n    # When the state length equals the number of elements, record the solution\n    if len(state) == len(choices):\n        res.append(list(state))\n        return\n    # Traverse all choices\n    for i, choice in enumerate(choices):\n        # Pruning: do not allow repeated selection of elements\n        if not selected[i]:\n            # Attempt: make choice, update state\n            selected[i] = True\n            state.append(choice)\n            # Proceed to the next round of selection\n            backtrack(state, choices, selected, res)\n            # Backtrack: undo choice, restore to previous state\n            selected[i] = False\n            state.pop()\n\ndef permutations_i(nums: list[int]) -> list[list[int]]:\n    \"\"\"Permutations I\"\"\"\n    res = []\n    backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)\n    return res\n
permutations_i.cpp
/* Backtracking algorithm: Permutations I */\nvoid backtrack(vector<int> &state, const vector<int> &choices, vector<bool> &selected, vector<vector<int>> &res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size() == choices.size()) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choices.size(); i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.push_back(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop_back();\n        }\n    }\n}\n\n/* Permutations I */\nvector<vector<int>> permutationsI(vector<int> nums) {\n    vector<int> state;\n    vector<bool> selected(nums.size(), false);\n    vector<vector<int>> res;\n    backtrack(state, nums, selected, res);\n    return res;\n}\n
permutations_i.java
/* Backtracking algorithm: Permutations I */\nvoid backtrack(List<Integer> state, int[] choices, boolean[] selected, List<List<Integer>> res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size() == choices.length) {\n        res.add(new ArrayList<Integer>(state));\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choices.length; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.add(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.remove(state.size() - 1);\n        }\n    }\n}\n\n/* Permutations I */\nList<List<Integer>> permutationsI(int[] nums) {\n    List<List<Integer>> res = new ArrayList<List<Integer>>();\n    backtrack(new ArrayList<Integer>(), nums, new boolean[nums.length], res);\n    return res;\n}\n
permutations_i.cs
/* Backtracking algorithm: Permutations I */\nvoid Backtrack(List<int> state, int[] choices, bool[] selected, List<List<int>> res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.Count == choices.Length) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choices.Length; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.Add(choice);\n            // Proceed to the next round of selection\n            Backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.RemoveAt(state.Count - 1);\n        }\n    }\n}\n\n/* Permutations I */\nList<List<int>> PermutationsI(int[] nums) {\n    List<List<int>> res = [];\n    Backtrack([], nums, new bool[nums.Length], res);\n    return res;\n}\n
permutations_i.go
/* Backtracking algorithm: Permutations I */\nfunc backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {\n    // When the state length equals the number of elements, record the solution\n    if len(*state) == len(*choices) {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n    }\n    // Traverse all choices\n    for i := 0; i < len(*choices); i++ {\n        choice := (*choices)[i]\n        // Pruning: do not allow repeated selection of elements\n        if !(*selected)[i] {\n            // Attempt: make choice, update state\n            (*selected)[i] = true\n            *state = append(*state, choice)\n            // Proceed to the next round of selection\n            backtrackI(state, choices, selected, res)\n            // Backtrack: undo choice, restore to previous state\n            (*selected)[i] = false\n            *state = (*state)[:len(*state)-1]\n        }\n    }\n}\n\n/* Permutations I */\nfunc permutationsI(nums []int) [][]int {\n    res := make([][]int, 0)\n    state := make([]int, 0)\n    selected := make([]bool, len(nums))\n    backtrackI(&state, &nums, &selected, &res)\n    return res\n}\n
permutations_i.swift
/* Backtracking algorithm: Permutations I */\nfunc backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {\n    // When the state length equals the number of elements, record the solution\n    if state.count == choices.count {\n        res.append(state)\n        return\n    }\n    // Traverse all choices\n    for (i, choice) in choices.enumerated() {\n        // Pruning: do not allow repeated selection of elements\n        if !selected[i] {\n            // Attempt: make choice, update state\n            selected[i] = true\n            state.append(choice)\n            // Proceed to the next round of selection\n            backtrack(state: &state, choices: choices, selected: &selected, res: &res)\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false\n            state.removeLast()\n        }\n    }\n}\n\n/* Permutations I */\nfunc permutationsI(nums: [Int]) -> [[Int]] {\n    var state: [Int] = []\n    var selected = Array(repeating: false, count: nums.count)\n    var res: [[Int]] = []\n    backtrack(state: &state, choices: nums, selected: &selected, res: &res)\n    return res\n}\n
permutations_i.js
/* Backtracking algorithm: Permutations I */\nfunction backtrack(state, choices, selected, res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    choices.forEach((choice, i) => {\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations I */\nfunction permutationsI(nums) {\n    const res = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
permutations_i.ts
/* Backtracking algorithm: Permutations I */\nfunction backtrack(\n    state: number[],\n    choices: number[],\n    selected: boolean[],\n    res: number[][]\n): void {\n    // When the state length equals the number of elements, record the solution\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    choices.forEach((choice, i) => {\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations I */\nfunction permutationsI(nums: number[]): number[][] {\n    const res: number[][] = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
permutations_i.dart
/* Backtracking algorithm: Permutations I */\nvoid backtrack(\n  List<int> state,\n  List<int> choices,\n  List<bool> selected,\n  List<List<int>> res,\n) {\n  // When the state length equals the number of elements, record the solution\n  if (state.length == choices.length) {\n    res.add(List.from(state));\n    return;\n  }\n  // Traverse all choices\n  for (int i = 0; i < choices.length; i++) {\n    int choice = choices[i];\n    // Pruning: do not allow repeated selection of elements\n    if (!selected[i]) {\n      // Attempt: make choice, update state\n      selected[i] = true;\n      state.add(choice);\n      // Proceed to the next round of selection\n      backtrack(state, choices, selected, res);\n      // Backtrack: undo choice, restore to previous state\n      selected[i] = false;\n      state.removeLast();\n    }\n  }\n}\n\n/* Permutations I */\nList<List<int>> permutationsI(List<int> nums) {\n  List<List<int>> res = [];\n  backtrack([], nums, List.filled(nums.length, false), res);\n  return res;\n}\n
permutations_i.rs
/* Backtracking algorithm: Permutations I */\nfn backtrack(mut state: Vec<i32>, choices: &[i32], selected: &mut [bool], res: &mut Vec<Vec<i32>>) {\n    // When the state length equals the number of elements, record the solution\n    if state.len() == choices.len() {\n        res.push(state);\n        return;\n    }\n    // Traverse all choices\n    for i in 0..choices.len() {\n        let choice = choices[i];\n        // Pruning: do not allow repeated selection of elements\n        if !selected[i] {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state.clone(), choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    }\n}\n\n/* Permutations I */\nfn permutations_i(nums: &mut [i32]) -> Vec<Vec<i32>> {\n    let mut res = Vec::new(); // State (subset)\n    backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res);\n    res\n}\n
permutations_i.c
/* Backtracking algorithm: Permutations I */\nvoid backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {\n    // When the state length equals the number of elements, record the solution\n    if (stateSize == choicesSize) {\n        res[*resSize] = (int *)malloc(choicesSize * sizeof(int));\n        for (int i = 0; i < choicesSize; i++) {\n            res[*resSize][i] = state[i];\n        }\n        (*resSize)++;\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choicesSize; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state[stateSize] = choice;\n            // Proceed to the next round of selection\n            backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n        }\n    }\n}\n\n/* Permutations I */\nint **permutationsI(int *nums, int numsSize, int *returnSize) {\n    int *state = (int *)malloc(numsSize * sizeof(int));\n    bool *selected = (bool *)malloc(numsSize * sizeof(bool));\n    for (int i = 0; i < numsSize; i++) {\n        selected[i] = false;\n    }\n    int **res = (int **)malloc(MAX_SIZE * sizeof(int *));\n    *returnSize = 0;\n\n    backtrack(state, 0, nums, numsSize, selected, res, returnSize);\n\n    free(state);\n    free(selected);\n\n    return res;\n}\n
permutations_i.kt
/* Backtracking algorithm: Permutations I */\nfun backtrack(\n    state: MutableList<Int>,\n    choices: IntArray,\n    selected: BooleanArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size == choices.size) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Traverse all choices\n    for (i in choices.indices) {\n        val choice = choices[i]\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true\n            state.add(choice)\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res)\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false\n            state.removeAt(state.size - 1)\n        }\n    }\n}\n\n/* Permutations I */\nfun permutationsI(nums: IntArray): MutableList<MutableList<Int>?> {\n    val res = mutableListOf<MutableList<Int>?>()\n    backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)\n    return res\n}\n
permutations_i.rb
### Backtracking: permutations I ###\ndef backtrack(state, choices, selected, res)\n  # When the state length equals the number of elements, record the solution\n  if state.length == choices.length\n    res << state.dup\n    return\n  end\n\n  # Traverse all choices\n  choices.each_with_index do |choice, i|\n    # Pruning: do not allow repeated selection of elements\n    unless selected[i]\n      # Attempt: make choice, update state\n      selected[i] = true\n      state << choice\n      # Proceed to the next round of selection\n      backtrack(state, choices, selected, res)\n      # Backtrack: undo choice, restore to previous state\n      selected[i] = false\n      state.pop\n    end\n  end\nend\n\n### Permutations I ###\ndef permutations_i(nums)\n  res = []\n  backtrack([], nums, Array.new(nums.length, false), res)\n  res\nend\n
","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1322-case-with-duplicate-elements","level":2,"title":"13.2.2   Case with Duplicate Elements","text":"

Question

Given an integer array that may contain duplicate elements, return all unique permutations.

Suppose the input array is \\([1, 1, 2]\\). To distinguish the two duplicate elements \\(1\\), we denote the second \\(1\\) as \\(\\hat{1}\\).

As shown in Figure 13-7, the method described above generates permutations where half are duplicates.

Figure 13-7   Duplicate permutations

So how do we remove duplicate permutations? The most direct approach is to use a hash set to directly deduplicate the permutation results. However, this is not elegant because the search branches that generate duplicate permutations are unnecessary and should be identified and pruned early, which can further improve algorithm efficiency.

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1-pruning-duplicate-elements","level":3,"title":"1.   Pruning Duplicate Elements","text":"

Observe Figure 13-8. In the first round, choosing \\(1\\) or choosing \\(\\hat{1}\\) is equivalent. All permutations generated under these two choices are duplicates. Therefore, we should prune \\(\\hat{1}\\).

Similarly, after choosing \\(2\\) in the first round, the \\(1\\) and \\(\\hat{1}\\) in the second round also produce duplicate branches, so the second round's \\(\\hat{1}\\) should also be pruned.

Essentially, our goal is to ensure that multiple equal elements are chosen only once in a certain round of choices.

Figure 13-8   Pruning duplicate permutations

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#2-code-implementation_1","level":3,"title":"2.   Code Implementation","text":"

Building on the code from the previous problem, we consider opening a hash set duplicated in each round of choices to record which elements have been tried in this round, and prune duplicate elements:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby permutations_ii.py
def backtrack(\n    state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]\n):\n    \"\"\"Backtracking algorithm: Permutations II\"\"\"\n    # When the state length equals the number of elements, record the solution\n    if len(state) == len(choices):\n        res.append(list(state))\n        return\n    # Traverse all choices\n    duplicated = set[int]()\n    for i, choice in enumerate(choices):\n        # Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if not selected[i] and choice not in duplicated:\n            # Attempt: make choice, update state\n            duplicated.add(choice)  # Record the selected element value\n            selected[i] = True\n            state.append(choice)\n            # Proceed to the next round of selection\n            backtrack(state, choices, selected, res)\n            # Backtrack: undo choice, restore to previous state\n            selected[i] = False\n            state.pop()\n\ndef permutations_ii(nums: list[int]) -> list[list[int]]:\n    \"\"\"Permutations II\"\"\"\n    res = []\n    backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)\n    return res\n
permutations_ii.cpp
/* Backtracking algorithm: Permutations II */\nvoid backtrack(vector<int> &state, const vector<int> &choices, vector<bool> &selected, vector<vector<int>> &res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size() == choices.size()) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all choices\n    unordered_set<int> duplicated;\n    for (int i = 0; i < choices.size(); i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && duplicated.find(choice) == duplicated.end()) {\n            // Attempt: make choice, update state\n            duplicated.emplace(choice); // Record the selected element value\n            selected[i] = true;\n            state.push_back(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop_back();\n        }\n    }\n}\n\n/* Permutations II */\nvector<vector<int>> permutationsII(vector<int> nums) {\n    vector<int> state;\n    vector<bool> selected(nums.size(), false);\n    vector<vector<int>> res;\n    backtrack(state, nums, selected, res);\n    return res;\n}\n
permutations_ii.java
/* Backtracking algorithm: Permutations II */\nvoid backtrack(List<Integer> state, int[] choices, boolean[] selected, List<List<Integer>> res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size() == choices.length) {\n        res.add(new ArrayList<Integer>(state));\n        return;\n    }\n    // Traverse all choices\n    Set<Integer> duplicated = new HashSet<Integer>();\n    for (int i = 0; i < choices.length; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated.contains(choice)) {\n            // Attempt: make choice, update state\n            duplicated.add(choice); // Record the selected element value\n            selected[i] = true;\n            state.add(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.remove(state.size() - 1);\n        }\n    }\n}\n\n/* Permutations II */\nList<List<Integer>> permutationsII(int[] nums) {\n    List<List<Integer>> res = new ArrayList<List<Integer>>();\n    backtrack(new ArrayList<Integer>(), nums, new boolean[nums.length], res);\n    return res;\n}\n
permutations_ii.cs
/* Backtracking algorithm: Permutations II */\nvoid Backtrack(List<int> state, int[] choices, bool[] selected, List<List<int>> res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.Count == choices.Length) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Traverse all choices\n    HashSet<int> duplicated = [];\n    for (int i = 0; i < choices.Length; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated.Contains(choice)) {\n            // Attempt: make choice, update state\n            duplicated.Add(choice); // Record the selected element value\n            selected[i] = true;\n            state.Add(choice);\n            // Proceed to the next round of selection\n            Backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.RemoveAt(state.Count - 1);\n        }\n    }\n}\n\n/* Permutations II */\nList<List<int>> PermutationsII(int[] nums) {\n    List<List<int>> res = [];\n    Backtrack([], nums, new bool[nums.Length], res);\n    return res;\n}\n
permutations_ii.go
/* Backtracking algorithm: Permutations II */\nfunc backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {\n    // When the state length equals the number of elements, record the solution\n    if len(*state) == len(*choices) {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n    }\n    // Traverse all choices\n    duplicated := make(map[int]struct{}, 0)\n    for i := 0; i < len(*choices); i++ {\n        choice := (*choices)[i]\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if _, ok := duplicated[choice]; !ok && !(*selected)[i] {\n            // Attempt: make choice, update state\n            // Record the selected element value\n            duplicated[choice] = struct{}{}\n            (*selected)[i] = true\n            *state = append(*state, choice)\n            // Proceed to the next round of selection\n            backtrackII(state, choices, selected, res)\n            // Backtrack: undo choice, restore to previous state\n            (*selected)[i] = false\n            *state = (*state)[:len(*state)-1]\n        }\n    }\n}\n\n/* Permutations II */\nfunc permutationsII(nums []int) [][]int {\n    res := make([][]int, 0)\n    state := make([]int, 0)\n    selected := make([]bool, len(nums))\n    backtrackII(&state, &nums, &selected, &res)\n    return res\n}\n
permutations_ii.swift
/* Backtracking algorithm: Permutations II */\nfunc backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {\n    // When the state length equals the number of elements, record the solution\n    if state.count == choices.count {\n        res.append(state)\n        return\n    }\n    // Traverse all choices\n    var duplicated: Set<Int> = []\n    for (i, choice) in choices.enumerated() {\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if !selected[i], !duplicated.contains(choice) {\n            // Attempt: make choice, update state\n            duplicated.insert(choice) // Record the selected element value\n            selected[i] = true\n            state.append(choice)\n            // Proceed to the next round of selection\n            backtrack(state: &state, choices: choices, selected: &selected, res: &res)\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false\n            state.removeLast()\n        }\n    }\n}\n\n/* Permutations II */\nfunc permutationsII(nums: [Int]) -> [[Int]] {\n    var state: [Int] = []\n    var selected = Array(repeating: false, count: nums.count)\n    var res: [[Int]] = []\n    backtrack(state: &state, choices: nums, selected: &selected, res: &res)\n    return res\n}\n
permutations_ii.js
/* Backtracking algorithm: Permutations II */\nfunction backtrack(state, choices, selected, res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    const duplicated = new Set();\n    choices.forEach((choice, i) => {\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated.has(choice)) {\n            // Attempt: make choice, update state\n            duplicated.add(choice); // Record the selected element value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations II */\nfunction permutationsII(nums) {\n    const res = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
permutations_ii.ts
/* Backtracking algorithm: Permutations II */\nfunction backtrack(\n    state: number[],\n    choices: number[],\n    selected: boolean[],\n    res: number[][]\n): void {\n    // When the state length equals the number of elements, record the solution\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    const duplicated = new Set();\n    choices.forEach((choice, i) => {\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated.has(choice)) {\n            // Attempt: make choice, update state\n            duplicated.add(choice); // Record the selected element value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations II */\nfunction permutationsII(nums: number[]): number[][] {\n    const res: number[][] = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
permutations_ii.dart
/* Backtracking algorithm: Permutations II */\nvoid backtrack(\n  List<int> state,\n  List<int> choices,\n  List<bool> selected,\n  List<List<int>> res,\n) {\n  // When the state length equals the number of elements, record the solution\n  if (state.length == choices.length) {\n    res.add(List.from(state));\n    return;\n  }\n  // Traverse all choices\n  Set<int> duplicated = {};\n  for (int i = 0; i < choices.length; i++) {\n    int choice = choices[i];\n    // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n    if (!selected[i] && !duplicated.contains(choice)) {\n      // Attempt: make choice, update state\n      duplicated.add(choice); // Record the selected element value\n      selected[i] = true;\n      state.add(choice);\n      // Proceed to the next round of selection\n      backtrack(state, choices, selected, res);\n      // Backtrack: undo choice, restore to previous state\n      selected[i] = false;\n      state.removeLast();\n    }\n  }\n}\n\n/* Permutations II */\nList<List<int>> permutationsII(List<int> nums) {\n  List<List<int>> res = [];\n  backtrack([], nums, List.filled(nums.length, false), res);\n  return res;\n}\n
permutations_ii.rs
/* Backtracking algorithm: Permutations II */\nfn backtrack(mut state: Vec<i32>, choices: &[i32], selected: &mut [bool], res: &mut Vec<Vec<i32>>) {\n    // When the state length equals the number of elements, record the solution\n    if state.len() == choices.len() {\n        res.push(state);\n        return;\n    }\n    // Traverse all choices\n    let mut duplicated = HashSet::<i32>::new();\n    for i in 0..choices.len() {\n        let choice = choices[i];\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if !selected[i] && !duplicated.contains(&choice) {\n            // Attempt: make choice, update state\n            duplicated.insert(choice); // Record the selected element value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state.clone(), choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    }\n}\n\n/* Permutations II */\nfn permutations_ii(nums: &mut [i32]) -> Vec<Vec<i32>> {\n    let mut res = Vec::new();\n    backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res);\n    res\n}\n
permutations_ii.c
/* Backtracking algorithm: Permutations II */\nvoid backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {\n    // When the state length equals the number of elements, record the solution\n    if (stateSize == choicesSize) {\n        res[*resSize] = (int *)malloc(choicesSize * sizeof(int));\n        for (int i = 0; i < choicesSize; i++) {\n            res[*resSize][i] = state[i];\n        }\n        (*resSize)++;\n        return;\n    }\n    // Traverse all choices\n    bool duplicated[MAX_SIZE] = {false};\n    for (int i = 0; i < choicesSize; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated[choice]) {\n            // Attempt: make choice, update state\n            duplicated[choice] = true; // Record the selected element value\n            selected[i] = true;\n            state[stateSize] = choice;\n            // Proceed to the next round of selection\n            backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n        }\n    }\n}\n\n/* Permutations II */\nint **permutationsII(int *nums, int numsSize, int *returnSize) {\n    int *state = (int *)malloc(numsSize * sizeof(int));\n    bool *selected = (bool *)malloc(numsSize * sizeof(bool));\n    for (int i = 0; i < numsSize; i++) {\n        selected[i] = false;\n    }\n    int **res = (int **)malloc(MAX_SIZE * sizeof(int *));\n    *returnSize = 0;\n\n    backtrack(state, 0, nums, numsSize, selected, res, returnSize);\n\n    free(state);\n    free(selected);\n\n    return res;\n}\n
permutations_ii.kt
/* Backtracking algorithm: Permutations II */\nfun backtrack(\n    state: MutableList<Int>,\n    choices: IntArray,\n    selected: BooleanArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size == choices.size) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Traverse all choices\n    val duplicated = HashSet<Int>()\n    for (i in choices.indices) {\n        val choice = choices[i]\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated.contains(choice)) {\n            // Attempt: make choice, update state\n            duplicated.add(choice) // Record the selected element value\n            selected[i] = true\n            state.add(choice)\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res)\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false\n            state.removeAt(state.size - 1)\n        }\n    }\n}\n\n/* Permutations II */\nfun permutationsII(nums: IntArray): MutableList<MutableList<Int>?> {\n    val res = mutableListOf<MutableList<Int>?>()\n    backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)\n    return res\n}\n
permutations_ii.rb
### Backtracking: permutations II ###\ndef backtrack(state, choices, selected, res)\n  # When the state length equals the number of elements, record the solution\n  if state.length == choices.length\n    res << state.dup\n    return\n  end\n\n  # Traverse all choices\n  duplicated = Set.new\n  choices.each_with_index do |choice, i|\n    # Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n    if !selected[i] && !duplicated.include?(choice)\n      # Attempt: make choice, update state\n      duplicated.add(choice)\n      selected[i] = true\n      state << choice\n      # Proceed to the next round of selection\n      backtrack(state, choices, selected, res)\n      # Backtrack: undo choice, restore to previous state\n      selected[i] = false\n      state.pop\n    end\n  end\nend\n\n### Permutations II ###\ndef permutations_ii(nums)\n  res = []\n  backtrack([], nums, Array.new(nums.length, false), res)\n  res\nend\n

Assuming elements are pairwise distinct, there are \\(n!\\) (factorial) permutations of \\(n\\) elements. When recording results, we need to copy a list of length \\(n\\), using \\(O(n)\\) time. Therefore, the time complexity is \\(O(n! \\cdot n)\\).

The maximum recursion depth is \\(n\\), using \\(O(n)\\) stack frame space. selected uses \\(O(n)\\) space. At most \\(n\\) duplicated sets exist simultaneously, using \\(O(n^2)\\) space. Therefore, the space complexity is \\(O(n^2)\\).

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#3-comparison-of-two-pruning-methods","level":3,"title":"3.   Comparison of Two Pruning Methods","text":"

Note that although both selected and duplicated are used for pruning, they have different objectives.

  • Pruning duplicate choices: There is only one selected throughout the entire search process. It records which elements are included in the current state, and its purpose is to prevent an element from appearing repeatedly in state.
  • Pruning duplicate elements: Each round of choices (each backtrack function call) contains a duplicated set. It records which elements have been chosen in this round's iteration (the for loop), and its purpose is to ensure that equal elements are chosen only once.

Figure 13-9 shows the effective scope of the two pruning conditions. Note that each node in the tree represents a choice, and the nodes on the path from the root to a leaf node form a permutation.

Figure 13-9   Effective scope of two pruning conditions

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/","level":1,"title":"13.3   Subset-Sum Problem","text":"","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1331-without-duplicate-elements","level":2,"title":"13.3.1   Without Duplicate Elements","text":"

Question

Given a positive integer array nums and a target positive integer target, find all possible combinations where the sum of elements in the combination equals target. The given array has no duplicate elements, and each element can be selected multiple times. Return these combinations in list form, where the list should not contain duplicate combinations.

For example, given the set \\(\\{3, 4, 5\\}\\) and target integer \\(9\\), the solutions are \\(\\{3, 3, 3\\}, \\{4, 5\\}\\). Note the following two points:

  • Elements in the input set can be selected repeatedly without limit.
  • Subsets do not distinguish element order; for example, \\(\\{4, 5\\}\\) and \\(\\{5, 4\\}\\) are the same subset.
","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1-reference-to-full-permutation-solution","level":3,"title":"1.   Reference to Full Permutation Solution","text":"

Similar to the full permutation problem, we can imagine the process of generating subsets as a series of choices, and update the \"sum of elements\" in real-time during the selection process. When the sum equals target, we record the subset to the result list.

Unlike the full permutation problem, elements in this problem's set can be selected unlimited times, so we do not need to use a selected boolean list to track whether an element has been selected. We can make minor modifications to the full permutation code and initially obtain the solution:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_i_naive.py
def backtrack(\n    state: list[int],\n    target: int,\n    total: int,\n    choices: list[int],\n    res: list[list[int]],\n):\n    \"\"\"Backtracking algorithm: Subset sum I\"\"\"\n    # When the subset sum equals target, record the solution\n    if total == target:\n        res.append(list(state))\n        return\n    # Traverse all choices\n    for i in range(len(choices)):\n        # Pruning: if the subset sum exceeds target, skip this choice\n        if total + choices[i] > target:\n            continue\n        # Attempt: make choice, update element sum total\n        state.append(choices[i])\n        # Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res)\n        # Backtrack: undo choice, restore to previous state\n        state.pop()\n\ndef subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum I (including duplicate subsets)\"\"\"\n    state = []  # State (subset)\n    total = 0  # Subset sum\n    res = []  # Result list (subset list)\n    backtrack(state, target, total, nums, res)\n    return res\n
subset_sum_i_naive.cpp
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(vector<int> &state, int target, int total, vector<int> &choices, vector<vector<int>> &res) {\n    // When the subset sum equals target, record the solution\n    if (total == target) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all choices\n    for (size_t i = 0; i < choices.size(); i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.push_back(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nvector<vector<int>> subsetSumINaive(vector<int> &nums, int target) {\n    vector<int> state;       // State (subset)\n    int total = 0;           // Subset sum\n    vector<vector<int>> res; // Result list (subset list)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
subset_sum_i_naive.java
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(List<Integer> state, int target, int total, int[] choices, List<List<Integer>> res) {\n    // When the subset sum equals target, record the solution\n    if (total == target) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choices.length; i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.add(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nList<List<Integer>> subsetSumINaive(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    int total = 0; // Subset sum\n    List<List<Integer>> res = new ArrayList<>(); // Result list (subset list)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
subset_sum_i_naive.cs
/* Backtracking algorithm: Subset sum I */\nvoid Backtrack(List<int> state, int target, int total, int[] choices, List<List<int>> res) {\n    // When the subset sum equals target, record the solution\n    if (total == target) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choices.Length; i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.Add(choices[i]);\n        // Proceed to the next round of selection\n        Backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nList<List<int>> SubsetSumINaive(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    int total = 0; // Subset sum\n    List<List<int>> res = []; // Result list (subset list)\n    Backtrack(state, target, total, nums, res);\n    return res;\n}\n
subset_sum_i_naive.go
/* Backtracking algorithm: Subset sum I */\nfunc backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) {\n    // When the subset sum equals target, record the solution\n    if target == total {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Traverse all choices\n    for i := 0; i < len(*choices); i++ {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if total+(*choices)[i] > target {\n            continue\n        }\n        // Attempt: make choice, update element sum total\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next round of selection\n        backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res)\n        // Backtrack: undo choice, restore to previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfunc subsetSumINaive(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    total := 0              // Subset sum\n    res := make([][]int, 0) // Result list (subset list)\n    backtrackSubsetSumINaive(total, target, &state, &nums, &res)\n    return res\n}\n
subset_sum_i_naive.swift
/* Backtracking algorithm: Subset sum I */\nfunc backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) {\n    // When the subset sum equals target, record the solution\n    if total == target {\n        res.append(state)\n        return\n    }\n    // Traverse all choices\n    for i in choices.indices {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if total + choices[i] > target {\n            continue\n        }\n        // Attempt: make choice, update element sum total\n        state.append(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfunc subsetSumINaive(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let total = 0 // Subset sum\n    var res: [[Int]] = [] // Result list (subset list)\n    backtrack(state: &state, target: target, total: total, choices: nums, res: &res)\n    return res\n}\n
subset_sum_i_naive.js
/* Backtracking algorithm: Subset sum I */\nfunction backtrack(state, target, total, choices, res) {\n    // When the subset sum equals target, record the solution\n    if (total === target) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    for (let i = 0; i < choices.length; i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfunction subsetSumINaive(nums, target) {\n    const state = []; // State (subset)\n    const total = 0; // Subset sum\n    const res = []; // Result list (subset list)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
subset_sum_i_naive.ts
/* Backtracking algorithm: Subset sum I */\nfunction backtrack(\n    state: number[],\n    target: number,\n    total: number,\n    choices: number[],\n    res: number[][]\n): void {\n    // When the subset sum equals target, record the solution\n    if (total === target) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    for (let i = 0; i < choices.length; i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfunction subsetSumINaive(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    const total = 0; // Subset sum\n    const res = []; // Result list (subset list)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
subset_sum_i_naive.dart
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(\n  List<int> state,\n  int target,\n  int total,\n  List<int> choices,\n  List<List<int>> res,\n) {\n  // When the subset sum equals target, record the solution\n  if (total == target) {\n    res.add(List.from(state));\n    return;\n  }\n  // Traverse all choices\n  for (int i = 0; i < choices.length; i++) {\n    // Pruning: if the subset sum exceeds target, skip this choice\n    if (total + choices[i] > target) {\n      continue;\n    }\n    // Attempt: make choice, update element sum total\n    state.add(choices[i]);\n    // Proceed to the next round of selection\n    backtrack(state, target, total + choices[i], choices, res);\n    // Backtrack: undo choice, restore to previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nList<List<int>> subsetSumINaive(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  int total = 0; // Sum of elements\n  List<List<int>> res = []; // Result list (subset list)\n  backtrack(state, target, total, nums, res);\n  return res;\n}\n
subset_sum_i_naive.rs
/* Backtracking algorithm: Subset sum I */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    total: i32,\n    choices: &[i32],\n    res: &mut Vec<Vec<i32>>,\n) {\n    // When the subset sum equals target, record the solution\n    if total == target {\n        res.push(state.clone());\n        return;\n    }\n    // Traverse all choices\n    for i in 0..choices.len() {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if total + choices[i] > target {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    let total = 0; // Subset sum\n    let mut res = Vec::new(); // Result list (subset list)\n    backtrack(&mut state, target, total, nums, &mut res);\n    res\n}\n
subset_sum_i_naive.c
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(int target, int total, int *choices, int choicesSize) {\n    // When the subset sum equals target, record the solution\n    if (total == target) {\n        for (int i = 0; i < stateSize; i++) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choicesSize; i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state[stateSize++] = choices[i];\n        // Proceed to the next round of selection\n        backtrack(target, total + choices[i], choices, choicesSize);\n        // Backtrack: undo choice, restore to previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nvoid subsetSumINaive(int *nums, int numsSize, int target) {\n    resSize = 0; // Initialize solution count to 0\n    backtrack(target, 0, nums, numsSize);\n}\n
subset_sum_i_naive.kt
/* Backtracking algorithm: Subset sum I */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    total: Int,\n    choices: IntArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // When the subset sum equals target, record the solution\n    if (total == target) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Traverse all choices\n    for (i in choices.indices) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue\n        }\n        // Attempt: make choice, update element sum total\n        state.add(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfun subsetSumINaive(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    val total = 0 // Subset sum\n    val res = mutableListOf<MutableList<Int>?>() // Result list (subset list)\n    backtrack(state, target, total, nums, res)\n    return res\n}\n
subset_sum_i_naive.rb
### Backtracking: subset sum I ###\ndef backtrack(state, target, total, choices, res)\n  # When the subset sum equals target, record the solution\n  if total == target\n    res << state.dup\n    return\n  end\n\n  # Traverse all choices\n  for i in 0...choices.length\n    # Pruning: if the subset sum exceeds target, skip this choice\n    next if total + choices[i] > target\n    # Attempt: make choice, update element sum total\n    state << choices[i]\n    # Proceed to the next round of selection\n    backtrack(state, target, total + choices[i], choices, res)\n    # Backtrack: undo choice, restore to previous state\n    state.pop\n  end\nend\n\n### Solve subset sum I (with duplicate subsets) ###\ndef subset_sum_i_naive(nums, target)\n  state = [] # State (subset)\n  total = 0 # Subset sum\n  res = [] # Result list (subset list)\n  backtrack(state, target, total, nums, res)\n  res\nend\n

When we input array \\([3, 4, 5]\\) and target element \\(9\\) to the above code, the output is \\([3, 3, 3], [4, 5], [5, 4]\\). Although we successfully find all subsets that sum to \\(9\\), there are duplicate subsets \\([4, 5]\\) and \\([5, 4]\\).

This is because the search process distinguishes the order of selections, but subsets do not distinguish selection order. As shown in Figure 13-10, selecting 4 first and then 5 versus selecting 5 first and then 4 are different branches, but they correspond to the same subset.

Figure 13-10   Subset search and boundary pruning

To eliminate duplicate subsets, one straightforward idea is to deduplicate the result list. However, this approach is very inefficient for two reasons:

  • When there are many array elements, especially when target is large, the search process generates many duplicate subsets.
  • Comparing subsets (arrays) is very time-consuming, requiring sorting the arrays first, then comparing each element in them.
","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#2-pruning-duplicate-subsets","level":3,"title":"2.   Pruning Duplicate Subsets","text":"

We consider deduplication through pruning during the search process. Observing Figure 13-11, duplicate subsets occur when array elements are selected in different orders, as in the following cases:

  1. When the first and second rounds select \\(3\\) and \\(4\\) respectively, all subsets containing these two elements are generated, denoted as \\([3, 4, \\dots]\\).
  2. Afterward, when the first round selects \\(4\\), the second round should skip \\(3\\), because the subset \\([4, 3, \\dots]\\) generated by this choice is completely duplicate with the subset generated in step 1.

In the search process, each level's choices are tried from left to right, so the rightmost branches are pruned more.

  1. The first two rounds select \\(3\\) and \\(5\\), generating subset \\([3, 5, \\dots]\\).
  2. The first two rounds select \\(4\\) and \\(5\\), generating subset \\([4, 5, \\dots]\\).
  3. If the first round selects \\(5\\), the second round should skip \\(3\\) and \\(4\\), because subsets \\([5, 3, \\dots]\\) and \\([5, 4, \\dots]\\) are completely duplicate with the subsets described in steps 1. and 2.

Figure 13-11   Different selection orders leading to duplicate subsets

In summary, given an input array \\([x_1, x_2, \\dots, x_n]\\), let the selection sequence in the search process be \\([x_{i_1}, x_{i_2}, \\dots, x_{i_m}]\\). This selection sequence must satisfy \\(i_1 \\leq i_2 \\leq \\dots \\leq i_m\\); any selection sequence that does not satisfy this condition will cause duplicates and should be pruned.

","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#3-code-implementation","level":3,"title":"3.   Code Implementation","text":"

To implement this pruning, we initialize a variable start to indicate the starting point of traversal. After making choice \\(x_{i}\\), set the next round to start traversal from index \\(i\\). This ensures that the selection sequence satisfies \\(i_1 \\leq i_2 \\leq \\dots \\leq i_m\\), guaranteeing subset uniqueness.

In addition, we have made the following two optimizations to the code:

  • Before starting the search, first sort the array nums. When traversing all choices, end the loop immediately when the subset sum exceeds target, because subsequent elements are larger, and their subset sums must exceed target.
  • Omit the element sum variable total and use subtraction on target to track the sum of elements. Record the solution when target equals \\(0\\).
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_i.py
def backtrack(\n    state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]\n):\n    \"\"\"Backtracking algorithm: Subset sum I\"\"\"\n    # When the subset sum equals target, record the solution\n    if target == 0:\n        res.append(list(state))\n        return\n    # Traverse all choices\n    # Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for i in range(start, len(choices)):\n        # Pruning 1: if the subset sum exceeds target, end the loop directly\n        # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0:\n            break\n        # Attempt: make choice, update target, start\n        state.append(choices[i])\n        # Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res)\n        # Backtrack: undo choice, restore to previous state\n        state.pop()\n\ndef subset_sum_i(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum I\"\"\"\n    state = []  # State (subset)\n    nums.sort()  # Sort nums\n    start = 0  # Start point for traversal\n    res = []  # Result list (subset list)\n    backtrack(state, target, nums, start, res)\n    return res\n
subset_sum_i.cpp
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.size(); i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.push_back(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum I */\nvector<vector<int>> subsetSumI(vector<int> &nums, int target) {\n    vector<int> state;              // State (subset)\n    sort(nums.begin(), nums.end()); // Sort nums\n    int start = 0;                  // Start point for traversal\n    vector<vector<int>> res;        // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_i.java
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(List<Integer> state, int target, int[] choices, int start, List<List<Integer>> res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.add(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum I */\nList<List<Integer>> subsetSumI(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    Arrays.sort(nums); // Sort nums\n    int start = 0; // Start point for traversal\n    List<List<Integer>> res = new ArrayList<>(); // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_i.cs
/* Backtracking algorithm: Subset sum I */\nvoid Backtrack(List<int> state, int target, int[] choices, int start, List<List<int>> res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.Length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.Add(choices[i]);\n        // Proceed to the next round of selection\n        Backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum I */\nList<List<int>> SubsetSumI(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    Array.Sort(nums); // Sort nums\n    int start = 0; // Start point for traversal\n    List<List<int>> res = []; // Result list (subset list)\n    Backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_i.go
/* Backtracking algorithm: Subset sum I */\nfunc backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for i := start; i < len(*choices); i++ {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target-(*choices)[i] < 0 {\n            break\n        }\n        // Attempt: make choice, update target, start\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next round of selection\n        backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res)\n        // Backtrack: undo choice, restore to previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum I */\nfunc subsetSumI(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    sort.Ints(nums)         // Sort nums\n    start := 0              // Start point for traversal\n    res := make([][]int, 0) // Result list (subset list)\n    backtrackSubsetSumI(start, target, &state, &nums, &res)\n    return res\n}\n
subset_sum_i.swift
/* Backtracking algorithm: Subset sum I */\nfunc backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        res.append(state)\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for i in choices.indices.dropFirst(start) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0 {\n            break\n        }\n        // Attempt: make choice, update target, start\n        state.append(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum I */\nfunc subsetSumI(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let nums = nums.sorted() // Sort nums\n    let start = 0 // Start point for traversal\n    var res: [[Int]] = [] // Result list (subset list)\n    backtrack(state: &state, target: target, choices: nums, start: start, res: &res)\n    return res\n}\n
subset_sum_i.js
/* Backtracking algorithm: Subset sum I */\nfunction backtrack(state, target, choices, start, res) {\n    // When the subset sum equals target, record the solution\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfunction subsetSumI(nums, target) {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Start point for traversal\n    const res = []; // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_i.ts
/* Backtracking algorithm: Subset sum I */\nfunction backtrack(\n    state: number[],\n    target: number,\n    choices: number[],\n    start: number,\n    res: number[][]\n): void {\n    // When the subset sum equals target, record the solution\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfunction subsetSumI(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Start point for traversal\n    const res = []; // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_i.dart
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(\n  List<int> state,\n  int target,\n  List<int> choices,\n  int start,\n  List<List<int>> res,\n) {\n  // When the subset sum equals target, record the solution\n  if (target == 0) {\n    res.add(List.from(state));\n    return;\n  }\n  // Traverse all choices\n  // Pruning 2: start traversing from start to avoid generating duplicate subsets\n  for (int i = start; i < choices.length; i++) {\n    // Pruning 1: if the subset sum exceeds target, end the loop directly\n    // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n    if (target - choices[i] < 0) {\n      break;\n    }\n    // Attempt: make choice, update target, start\n    state.add(choices[i]);\n    // Proceed to the next round of selection\n    backtrack(state, target - choices[i], choices, i, res);\n    // Backtrack: undo choice, restore to previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum I */\nList<List<int>> subsetSumI(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  nums.sort(); // Sort nums\n  int start = 0; // Start point for traversal\n  List<List<int>> res = []; // Result list (subset list)\n  backtrack(state, target, nums, start, res);\n  return res;\n}\n
subset_sum_i.rs
/* Backtracking algorithm: Subset sum I */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    choices: &[i32],\n    start: usize,\n    res: &mut Vec<Vec<i32>>,\n) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        res.push(state.clone());\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for i in start..choices.len() {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0 {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfn subset_sum_i(nums: &mut [i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    nums.sort(); // Sort nums\n    let start = 0; // Start point for traversal\n    let mut res = Vec::new(); // Result list (subset list)\n    backtrack(&mut state, target, nums, start, &mut res);\n    res\n}\n
subset_sum_i.c
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(int target, int *choices, int choicesSize, int start) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        for (int i = 0; i < stateSize; ++i) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (int i = start; i < choicesSize; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state[stateSize] = choices[i];\n        stateSize++;\n        // Proceed to the next round of selection\n        backtrack(target - choices[i], choices, choicesSize, i);\n        // Backtrack: undo choice, restore to previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum I */\nvoid subsetSumI(int *nums, int numsSize, int target) {\n    qsort(nums, numsSize, sizeof(int), cmp); // Sort nums\n    int start = 0;                           // Start point for traversal\n    backtrack(target, nums, numsSize, start);\n}\n
subset_sum_i.kt
/* Backtracking algorithm: Subset sum I */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    choices: IntArray,\n    start: Int,\n    res: MutableList<MutableList<Int>?>\n) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (i in start..<choices.size) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break\n        }\n        // Attempt: make choice, update target, start\n        state.add(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum I */\nfun subsetSumI(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    nums.sort() // Sort nums\n    val start = 0 // Start point for traversal\n    val res = mutableListOf<MutableList<Int>?>() // Result list (subset list)\n    backtrack(state, target, nums, start, res)\n    return res\n}\n
subset_sum_i.rb
### Backtracking: subset sum I ###\ndef backtrack(state, target, choices, start, res)\n  # When the subset sum equals target, record the solution\n  if target.zero?\n    res << state.dup\n    return\n  end\n  # Traverse all choices\n  # Pruning 2: start traversing from start to avoid generating duplicate subsets\n  for i in start...choices.length\n    # Pruning 1: if the subset sum exceeds target, end the loop directly\n    # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n    break if target - choices[i] < 0\n    # Attempt: make choice, update target, start\n    state << choices[i]\n    # Proceed to the next round of selection\n    backtrack(state, target - choices[i], choices, i, res)\n    # Backtrack: undo choice, restore to previous state\n    state.pop\n  end\nend\n\n### Solve subset sum I ###\ndef subset_sum_i(nums, target)\n  state = [] # State (subset)\n  nums.sort! # Sort nums\n  start = 0 # Start point for traversal\n  res = [] # Result list (subset list)\n  backtrack(state, target, nums, start, res)\n  res\nend\n

Figure 13-12 shows the complete backtracking process when array \\([3, 4, 5]\\) and target element \\(9\\) are input to the above code.

Figure 13-12   Subset-sum I backtracking process

","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1332-with-duplicate-elements-in-array","level":2,"title":"13.3.2   With Duplicate Elements in Array","text":"

Question

Given a positive integer array nums and a target positive integer target, find all possible combinations where the sum of elements in the combination equals target. The given array may contain duplicate elements, and each element can be selected at most once. Return these combinations in list form, where the list should not contain duplicate combinations.

Compared to the previous problem, the input array in this problem may contain duplicate elements, which introduces new challenges. For example, given array \\([4, \\hat{4}, 5]\\) and target element \\(9\\), the output of the existing code is \\([4, 5], [\\hat{4}, 5]\\), which contains duplicate subsets.

The reason for this duplication is that equal elements are selected multiple times in a certain round. In Figure 13-13, the first round has three choices, two of which are \\(4\\), creating two duplicate search branches that output duplicate subsets. Similarly, the two \\(4\\)'s in the second round also produce duplicate subsets.

Figure 13-13   Duplicate subsets caused by equal elements

","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1-pruning-equal-elements","level":3,"title":"1.   Pruning Equal Elements","text":"

To solve this problem, we need to limit equal elements to be selected only once in each round. The implementation is quite clever: since the array is already sorted, equal elements are adjacent. This means that in a certain round of selection, if the current element equals the element to its left, it means this element has already been selected, so we skip the current element directly.

At the same time, this problem specifies that each array element can only be selected once. Fortunately, we can also use the variable start to satisfy this constraint: after making choice \\(x_{i}\\), set the next round to start traversal from index \\(i + 1\\) onwards. This both eliminates duplicate subsets and avoids selecting elements multiple times.

","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_ii.py
def backtrack(\n    state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]\n):\n    \"\"\"Backtracking algorithm: Subset sum II\"\"\"\n    # When the subset sum equals target, record the solution\n    if target == 0:\n        res.append(list(state))\n        return\n    # Traverse all choices\n    # Pruning 2: start traversing from start to avoid generating duplicate subsets\n    # Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for i in range(start, len(choices)):\n        # Pruning 1: if the subset sum exceeds target, end the loop directly\n        # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0:\n            break\n        # Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if i > start and choices[i] == choices[i - 1]:\n            continue\n        # Attempt: make choice, update target, start\n        state.append(choices[i])\n        # Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res)\n        # Backtrack: undo choice, restore to previous state\n        state.pop()\n\ndef subset_sum_ii(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum II\"\"\"\n    state = []  # State (subset)\n    nums.sort()  # Sort nums\n    start = 0  # Start point for traversal\n    res = []  # Result list (subset list)\n    backtrack(state, target, nums, start, res)\n    return res\n
subset_sum_ii.cpp
/* Backtracking algorithm: Subset sum II */\nvoid backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (int i = start; i < choices.size(); i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.push_back(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum II */\nvector<vector<int>> subsetSumII(vector<int> &nums, int target) {\n    vector<int> state;              // State (subset)\n    sort(nums.begin(), nums.end()); // Sort nums\n    int start = 0;                  // Start point for traversal\n    vector<vector<int>> res;        // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_ii.java
/* Backtracking algorithm: Subset sum II */\nvoid backtrack(List<Integer> state, int target, int[] choices, int start, List<List<Integer>> res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (int i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.add(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum II */\nList<List<Integer>> subsetSumII(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    Arrays.sort(nums); // Sort nums\n    int start = 0; // Start point for traversal\n    List<List<Integer>> res = new ArrayList<>(); // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_ii.cs
/* Backtracking algorithm: Subset sum II */\nvoid Backtrack(List<int> state, int target, int[] choices, int start, List<List<int>> res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (int i = start; i < choices.Length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.Add(choices[i]);\n        // Proceed to the next round of selection\n        Backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum II */\nList<List<int>> SubsetSumII(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    Array.Sort(nums); // Sort nums\n    int start = 0; // Start point for traversal\n    List<List<int>> res = []; // Result list (subset list)\n    Backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_ii.go
/* Backtracking algorithm: Subset sum II */\nfunc backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for i := start; i < len(*choices); i++ {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target-(*choices)[i] < 0 {\n            break\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if i > start && (*choices)[i] == (*choices)[i-1] {\n            continue\n        }\n        // Attempt: make choice, update target, start\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next round of selection\n        backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res)\n        // Backtrack: undo choice, restore to previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum II */\nfunc subsetSumII(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    sort.Ints(nums)         // Sort nums\n    start := 0              // Start point for traversal\n    res := make([][]int, 0) // Result list (subset list)\n    backtrackSubsetSumII(start, target, &state, &nums, &res)\n    return res\n}\n
subset_sum_ii.swift
/* Backtracking algorithm: Subset sum II */\nfunc backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        res.append(state)\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for i in choices.indices.dropFirst(start) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0 {\n            break\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if i > start, choices[i] == choices[i - 1] {\n            continue\n        }\n        // Attempt: make choice, update target, start\n        state.append(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum II */\nfunc subsetSumII(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let nums = nums.sorted() // Sort nums\n    let start = 0 // Start point for traversal\n    var res: [[Int]] = [] // Result list (subset list)\n    backtrack(state: &state, target: target, choices: nums, start: start, res: &res)\n    return res\n}\n
subset_sum_ii.js
/* Backtracking algorithm: Subset sum II */\nfunction backtrack(state, target, choices, start, res) {\n    // When the subset sum equals target, record the solution\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] === choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfunction subsetSumII(nums, target) {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Start point for traversal\n    const res = []; // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_ii.ts
/* Backtracking algorithm: Subset sum II */\nfunction backtrack(\n    state: number[],\n    target: number,\n    choices: number[],\n    start: number,\n    res: number[][]\n): void {\n    // When the subset sum equals target, record the solution\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] === choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfunction subsetSumII(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Start point for traversal\n    const res = []; // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_ii.dart
/* Backtracking algorithm: Subset sum II */\nvoid backtrack(\n  List<int> state,\n  int target,\n  List<int> choices,\n  int start,\n  List<List<int>> res,\n) {\n  // When the subset sum equals target, record the solution\n  if (target == 0) {\n    res.add(List.from(state));\n    return;\n  }\n  // Traverse all choices\n  // Pruning 2: start traversing from start to avoid generating duplicate subsets\n  // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n  for (int i = start; i < choices.length; i++) {\n    // Pruning 1: if the subset sum exceeds target, end the loop directly\n    // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n    if (target - choices[i] < 0) {\n      break;\n    }\n    // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n    if (i > start && choices[i] == choices[i - 1]) {\n      continue;\n    }\n    // Attempt: make choice, update target, start\n    state.add(choices[i]);\n    // Proceed to the next round of selection\n    backtrack(state, target - choices[i], choices, i + 1, res);\n    // Backtrack: undo choice, restore to previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum II */\nList<List<int>> subsetSumII(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  nums.sort(); // Sort nums\n  int start = 0; // Start point for traversal\n  List<List<int>> res = []; // Result list (subset list)\n  backtrack(state, target, nums, start, res);\n  return res;\n}\n
subset_sum_ii.rs
/* Backtracking algorithm: Subset sum II */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    choices: &[i32],\n    start: usize,\n    res: &mut Vec<Vec<i32>>,\n) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        res.push(state.clone());\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for i in start..choices.len() {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0 {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if i > start && choices[i] == choices[i - 1] {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    nums.sort(); // Sort nums\n    let start = 0; // Start point for traversal\n    let mut res = Vec::new(); // Result list (subset list)\n    backtrack(&mut state, target, nums, start, &mut res);\n    res\n}\n
subset_sum_ii.c
/* Backtracking algorithm: Subset sum II */\nvoid backtrack(int target, int *choices, int choicesSize, int start) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        for (int i = 0; i < stateSize; i++) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (int i = start; i < choicesSize; i++) {\n        // Pruning 1: Skip if subset sum exceeds target\n        if (target - choices[i] < 0) {\n            continue;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state[stateSize] = choices[i];\n        stateSize++;\n        // Proceed to the next round of selection\n        backtrack(target - choices[i], choices, choicesSize, i + 1);\n        // Backtrack: undo choice, restore to previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum II */\nvoid subsetSumII(int *nums, int numsSize, int target) {\n    // Sort nums\n    qsort(nums, numsSize, sizeof(int), cmp);\n    // Start backtracking\n    backtrack(target, nums, numsSize, 0);\n}\n
subset_sum_ii.kt
/* Backtracking algorithm: Subset sum II */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    choices: IntArray,\n    start: Int,\n    res: MutableList<MutableList<Int>?>\n) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (i in start..<choices.size) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue\n        }\n        // Attempt: make choice, update target, start\n        state.add(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum II */\nfun subsetSumII(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    nums.sort() // Sort nums\n    val start = 0 // Start point for traversal\n    val res = mutableListOf<MutableList<Int>?>() // Result list (subset list)\n    backtrack(state, target, nums, start, res)\n    return res\n}\n
subset_sum_ii.rb
### Backtracking: subset sum II ###\ndef backtrack(state, target, choices, start, res)\n  # When the subset sum equals target, record the solution\n  if target.zero?\n    res << state.dup\n    return\n  end\n\n  # Traverse all choices\n  # Pruning 2: start traversing from start to avoid generating duplicate subsets\n  # Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n  for i in start...choices.length\n    # Pruning 1: if the subset sum exceeds target, end the loop directly\n    # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n    break if target - choices[i] < 0\n    # Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n    next if i > start && choices[i] == choices[i - 1]\n    # Attempt: make choice, update target, start\n    state << choices[i]\n    # Proceed to the next round of selection\n    backtrack(state, target - choices[i], choices, i + 1, res)\n    # Backtrack: undo choice, restore to previous state\n    state.pop\n  end\nend\n\n### Solve subset sum II ###\ndef subset_sum_ii(nums, target)\n  state = [] # State (subset)\n  nums.sort! # Sort nums\n  start = 0 # Start point for traversal\n  res = [] # Result list (subset list)\n  backtrack(state, target, nums, start, res)\n  res\nend\n

Figure 13-14 shows the backtracking process for array \\([4, 4, 5]\\) and target element \\(9\\), which includes four types of pruning operations. Combine the illustration with the code comments to understand the entire search process and how each pruning operation works.

Figure 13-14   Subset-sum II backtracking process

","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/summary/","level":1,"title":"13.5   Summary","text":"","path":["Chapter 13. Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_backtracking/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • The backtracking algorithm is fundamentally an exhaustive search method. It finds solutions that meet specified conditions by performing a depth-first traversal of the solution space. During the search process, when a solution satisfying the conditions is found, it is recorded. The search ends either after finding all solutions or when the traversal is complete.
  • The backtracking algorithm search process consists of two parts: attempting and backtracking. It tries various choices through depth-first search. When encountering situations that violate constraints, it reverts the previous choice, returns to the previous state, and continues exploring other options. Attempting and backtracking are operations in opposite directions.
  • Backtracking problems typically contain multiple constraints, which can be utilized to implement pruning operations. Pruning can terminate unnecessary search branches early, significantly improving search efficiency.
  • The backtracking algorithm is primarily used to solve search problems and constraint satisfaction problems. While combinatorial optimization problems can be solved with backtracking, there are often more efficient or better-performing solutions available.
  • The permutation problem aims to find all possible permutations of elements in a given set. We use an array to record whether each element has been selected, thereby pruning search branches that attempt to select the same element repeatedly, ensuring each element is selected exactly once.
  • In the permutation problem, if the set contains duplicate elements, the final result will contain duplicate permutations. We need to impose a constraint so that equal elements can only be selected once per round, which is typically achieved using a hash set.
  • The subset-sum problem aims to find all subsets of a given set that sum to a target value. Since the set is unordered but the search process outputs results in all orders, duplicate subsets are generated. We sort the data before backtracking and use a variable to indicate the starting point of each round's traversal, thereby pruning search branches that generate duplicate subsets.
  • For the subset-sum problem, equal elements in the array produce duplicate sets. We leverage the precondition that the array is sorted by checking whether adjacent elements are equal to implement pruning, ensuring that equal elements can only be selected once per round.
  • The \\(n\\) queens problem aims to find placements of \\(n\\) queens on an \\(n \\times n\\) chessboard such that no two queens can attack each other. The constraints of this problem include row constraints, column constraints, and main and anti-diagonal constraints. To satisfy row constraints, we adopt a row-by-row placement strategy, ensuring exactly one queen is placed in each row.
  • The handling of column constraints and diagonal constraints is similar. For column constraints, we use an array to record whether each column has a queen, thereby indicating whether a selected cell is valid. For diagonal constraints, we use two arrays to separately record whether queens exist on each main or anti-diagonal. The challenge lies in finding the row-column index pattern that characterizes cells on the same main (anti-)diagonal.
","path":["Chapter 13. Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_backtracking/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: How should we understand the relationship between backtracking and recursion?

Overall, backtracking is an \"algorithm strategy\", while recursion is more like a \"tool\".

  • The backtracking algorithm is typically implemented based on recursion. However, backtracking is one application scenario of recursion and represents the application of recursion in search problems.
  • The structure of recursion embodies the \"subproblem decomposition\" problem-solving paradigm, commonly used to solve problems involving divide-and-conquer, backtracking, and dynamic programming (memoized recursion).
","path":["Chapter 13. Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/","level":1,"title":"Chapter 2.   Complexity Analysis","text":"

Abstract

Complexity analysis is like a space-time guide in the vast universe of algorithms.

It leads us to explore deeply within the two dimensions of time and space, seeking more elegant solutions.

","path":["Chapter 2. Complexity Analysis","Chapter 2.   Complexity Analysis"],"tags":[]},{"location":"chapter_computational_complexity/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 2.1   Algorithm Efficiency Evaluation
  • 2.2   Iteration and Recursion
  • 2.3   Time Complexity
  • 2.4   Space Complexity
  • 2.5   Summary
","path":["Chapter 2. Complexity Analysis","Chapter 2.   Complexity Analysis"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/","level":1,"title":"2.2   Iteration and Recursion","text":"

In algorithms, repeatedly executing a task is very common and closely related to complexity analysis. Therefore, before introducing time complexity and space complexity, let's first understand how to implement repeated task execution in programs, namely the two basic program control structures: iteration and recursion.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#221-iteration","level":2,"title":"2.2.1   Iteration","text":"

Iteration is a control structure for repeatedly executing a task. In iteration, a program repeatedly executes a segment of code under certain conditions until those conditions are no longer satisfied.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#1-for-loop","level":3,"title":"1.   For Loop","text":"

The for loop is one of the most common forms of iteration, suitable for use when the number of iterations is known in advance.

The following function implements the summation \\(1 + 2 + \\dots + n\\) based on a for loop, with the sum result recorded using the variable res. Note that in Python, range(a, b) corresponds to a \"left-closed, right-open\" interval, with the traversal range being \\(a, a + 1, \\dots, b-1\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
def for_loop(n: int) -> int:\n    \"\"\"for loop\"\"\"\n    res = 0\n    # Sum 1, 2, ..., n-1, n\n    for i in range(1, n + 1):\n        res += i\n    return res\n
iteration.cpp
/* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; ++i) {\n        res += i;\n    }\n    return res;\n}\n
iteration.java
/* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
iteration.cs
/* for loop */\nint ForLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
iteration.go
/* for loop */\nfunc forLoop(n int) int {\n    res := 0\n    // Sum 1, 2, ..., n-1, n\n    for i := 1; i <= n; i++ {\n        res += i\n    }\n    return res\n}\n
iteration.swift
/* for loop */\nfunc forLoop(n: Int) -> Int {\n    var res = 0\n    // Sum 1, 2, ..., n-1, n\n    for i in 1 ... n {\n        res += i\n    }\n    return res\n}\n
iteration.js
/* for loop */\nfunction forLoop(n) {\n    let res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
iteration.ts
/* for loop */\nfunction forLoop(n: number): number {\n    let res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
iteration.dart
/* for loop */\nint forLoop(int n) {\n  int res = 0;\n  // Sum 1, 2, ..., n-1, n\n  for (int i = 1; i <= n; i++) {\n    res += i;\n  }\n  return res;\n}\n
iteration.rs
/* for loop */\nfn for_loop(n: i32) -> i32 {\n    let mut res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for i in 1..=n {\n        res += i;\n    }\n    res\n}\n
iteration.c
/* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
iteration.kt
/* for loop */\nfun forLoop(n: Int): Int {\n    var res = 0\n    // Sum 1, 2, ..., n-1, n\n    for (i in 1..n) {\n        res += i\n    }\n    return res\n}\n
iteration.rb
### for loop ###\ndef for_loop(n)\n  res = 0\n\n  # Sum 1, 2, ..., n-1, n\n  for i in 1..n\n    res += i\n  end\n\n  res\nend\n

Figure 2-1 shows the flowchart of this summation function.

Figure 2-1   Flowchart of the summation function

The number of operations in this summation function is proportional to the input data size \\(n\\), or has a \"linear relationship\". In fact, time complexity describes precisely this \"linear relationship\". Related content will be introduced in detail in the next section.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-while-loop","level":3,"title":"2.   While Loop","text":"

Similar to the for loop, the while loop is also a method for implementing iteration. In a while loop, the program first checks the condition in each round; if the condition is true, it continues execution, otherwise it ends the loop.

Below we use a while loop to implement the summation \\(1 + 2 + \\dots + n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
def while_loop(n: int) -> int:\n    \"\"\"while loop\"\"\"\n    res = 0\n    i = 1  # Initialize condition variable\n    # Sum 1, 2, ..., n-1, n\n    while i <= n:\n        res += i\n        i += 1  # Update condition variable\n    return res\n
iteration.cpp
/* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i++; // Update condition variable\n    }\n    return res;\n}\n
iteration.java
/* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i++; // Update condition variable\n    }\n    return res;\n}\n
iteration.cs
/* while loop */\nint WhileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i += 1; // Update condition variable\n    }\n    return res;\n}\n
iteration.go
/* while loop */\nfunc whileLoop(n int) int {\n    res := 0\n    // Initialize condition variable\n    i := 1\n    // Sum 1, 2, ..., n-1, n\n    for i <= n {\n        res += i\n        // Update condition variable\n        i++\n    }\n    return res\n}\n
iteration.swift
/* while loop */\nfunc whileLoop(n: Int) -> Int {\n    var res = 0\n    var i = 1 // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while i <= n {\n        res += i\n        i += 1 // Update condition variable\n    }\n    return res\n}\n
iteration.js
/* while loop */\nfunction whileLoop(n) {\n    let res = 0;\n    let i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i++; // Update condition variable\n    }\n    return res;\n}\n
iteration.ts
/* while loop */\nfunction whileLoop(n: number): number {\n    let res = 0;\n    let i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i++; // Update condition variable\n    }\n    return res;\n}\n
iteration.dart
/* while loop */\nint whileLoop(int n) {\n  int res = 0;\n  int i = 1; // Initialize condition variable\n  // Sum 1, 2, ..., n-1, n\n  while (i <= n) {\n    res += i;\n    i++; // Update condition variable\n  }\n  return res;\n}\n
iteration.rs
/* while loop */\nfn while_loop(n: i32) -> i32 {\n    let mut res = 0;\n    let mut i = 1; // Initialize condition variable\n\n    // Sum 1, 2, ..., n-1, n\n    while i <= n {\n        res += i;\n        i += 1; // Update condition variable\n    }\n    res\n}\n
iteration.c
/* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i++; // Update condition variable\n    }\n    return res;\n}\n
iteration.kt
/* while loop */\nfun whileLoop(n: Int): Int {\n    var res = 0\n    var i = 1 // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i\n        i++ // Update condition variable\n    }\n    return res\n}\n
iteration.rb
### while loop ###\ndef while_loop(n)\n  res = 0\n  i = 1 # Initialize condition variable\n\n  # Sum 1, 2, ..., n-1, n\n  while i <= n\n    res += i\n    i += 1 # Update condition variable\n  end\n\n  res\nend\n

The while loop has greater flexibility than the for loop. In a while loop, we can freely design the initialization and update steps of the condition variable.

For example, in the following code, the condition variable \\(i\\) is updated twice per round, which is not convenient to implement using a for loop:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
def while_loop_ii(n: int) -> int:\n    \"\"\"while loop (two updates)\"\"\"\n    res = 0\n    i = 1  # Initialize condition variable\n    # Sum 1, 4, 10, ...\n    while i <= n:\n        res += i\n        # Update condition variable\n        i += 1\n        i *= 2\n    return res\n
iteration.cpp
/* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
iteration.java
/* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
iteration.cs
/* while loop (two updates) */\nint WhileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i += 1; \n        i *= 2;\n    }\n    return res;\n}\n
iteration.go
/* while loop (two updates) */\nfunc whileLoopII(n int) int {\n    res := 0\n    // Initialize condition variable\n    i := 1\n    // Sum 1, 4, 10, ...\n    for i <= n {\n        res += i\n        // Update condition variable\n        i++\n        i *= 2\n    }\n    return res\n}\n
iteration.swift
/* while loop (two updates) */\nfunc whileLoopII(n: Int) -> Int {\n    var res = 0\n    var i = 1 // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while i <= n {\n        res += i\n        // Update condition variable\n        i += 1\n        i *= 2\n    }\n    return res\n}\n
iteration.js
/* while loop (two updates) */\nfunction whileLoopII(n) {\n    let res = 0;\n    let i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
iteration.ts
/* while loop (two updates) */\nfunction whileLoopII(n: number): number {\n    let res = 0;\n    let i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
iteration.dart
/* while loop (two updates) */\nint whileLoopII(int n) {\n  int res = 0;\n  int i = 1; // Initialize condition variable\n  // Sum 1, 4, 10, ...\n  while (i <= n) {\n    res += i;\n    // Update condition variable\n    i++;\n    i *= 2;\n  }\n  return res;\n}\n
iteration.rs
/* while loop (two updates) */\nfn while_loop_ii(n: i32) -> i32 {\n    let mut res = 0;\n    let mut i = 1; // Initialize condition variable\n\n    // Sum 1, 4, 10, ...\n    while i <= n {\n        res += i;\n        // Update condition variable\n        i += 1;\n        i *= 2;\n    }\n    res\n}\n
iteration.c
/* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
iteration.kt
/* while loop (two updates) */\nfun whileLoopII(n: Int): Int {\n    var res = 0\n    var i = 1 // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i\n        // Update condition variable\n        i++\n        i *= 2\n    }\n    return res\n}\n
iteration.rb
### while loop (two updates) ###\ndef while_loop_ii(n)\n  res = 0\n  i = 1 # Initialize condition variable\n\n  # Sum 1, 4, 10, ...\n  while i <= n\n    res += i\n    # Update condition variable\n    i += 1\n    i *= 2\n  end\n\n  res\nend\n

Overall, for loops have more compact code, while while loops are more flexible; both can implement iterative structures. The choice of which to use should be determined based on the requirements of the specific problem.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#3-nested-loops","level":3,"title":"3.   Nested Loops","text":"

We can nest one loop structure inside another. Below is an example using for loops:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
def nested_for_loop(n: int) -> str:\n    \"\"\"Nested for loop\"\"\"\n    res = \"\"\n    # Loop i = 1, 2, ..., n-1, n\n    for i in range(1, n + 1):\n        # Loop j = 1, 2, ..., n-1, n\n        for j in range(1, n + 1):\n            res += f\"({i}, {j}), \"\n    return res\n
iteration.cpp
/* Nested for loop */\nstring nestedForLoop(int n) {\n    ostringstream res;\n    // Loop i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; ++i) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; ++j) {\n            res << \"(\" << i << \", \" << j << \"), \";\n        }\n    }\n    return res.str();\n}\n
iteration.java
/* Nested for loop */\nString nestedForLoop(int n) {\n    StringBuilder res = new StringBuilder();\n    // Loop i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            res.append(\"(\" + i + \", \" + j + \"), \");\n        }\n    }\n    return res.toString();\n}\n
iteration.cs
/* Nested for loop */\nstring NestedForLoop(int n) {\n    StringBuilder res = new();\n    // Loop i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            res.Append($\"({i}, {j}), \");\n        }\n    }\n    return res.ToString();\n}\n
iteration.go
/* Nested for loop */\nfunc nestedForLoop(n int) string {\n    res := \"\"\n    // Loop i = 1, 2, ..., n-1, n\n    for i := 1; i <= n; i++ {\n        for j := 1; j <= n; j++ {\n            // Loop j = 1, 2, ..., n-1, n\n            res += fmt.Sprintf(\"(%d, %d), \", i, j)\n        }\n    }\n    return res\n}\n
iteration.swift
/* Nested for loop */\nfunc nestedForLoop(n: Int) -> String {\n    var res = \"\"\n    // Loop i = 1, 2, ..., n-1, n\n    for i in 1 ... n {\n        // Loop j = 1, 2, ..., n-1, n\n        for j in 1 ... n {\n            res.append(\"(\\(i), \\(j)), \")\n        }\n    }\n    return res\n}\n
iteration.js
/* Nested for loop */\nfunction nestedForLoop(n) {\n    let res = '';\n    // Loop i = 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (let j = 1; j <= n; j++) {\n            res += `(${i}, ${j}), `;\n        }\n    }\n    return res;\n}\n
iteration.ts
/* Nested for loop */\nfunction nestedForLoop(n: number): string {\n    let res = '';\n    // Loop i = 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (let j = 1; j <= n; j++) {\n            res += `(${i}, ${j}), `;\n        }\n    }\n    return res;\n}\n
iteration.dart
/* Nested for loop */\nString nestedForLoop(int n) {\n  String res = \"\";\n  // Loop i = 1, 2, ..., n-1, n\n  for (int i = 1; i <= n; i++) {\n    // Loop j = 1, 2, ..., n-1, n\n    for (int j = 1; j <= n; j++) {\n      res += \"($i, $j), \";\n    }\n  }\n  return res;\n}\n
iteration.rs
/* Nested for loop */\nfn nested_for_loop(n: i32) -> String {\n    let mut res = vec![];\n    // Loop i = 1, 2, ..., n-1, n\n    for i in 1..=n {\n        // Loop j = 1, 2, ..., n-1, n\n        for j in 1..=n {\n            res.push(format!(\"({}, {}), \", i, j));\n        }\n    }\n    res.join(\"\")\n}\n
iteration.c
/* Nested for loop */\nchar *nestedForLoop(int n) {\n    // n * n is the number of points, \"(i, j), \" string max length is 6+10*2, plus extra space for null character \\0\n    int size = n * n * 26 + 1;\n    char *res = malloc(size * sizeof(char));\n    // Loop i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            char tmp[26];\n            snprintf(tmp, sizeof(tmp), \"(%d, %d), \", i, j);\n            strncat(res, tmp, size - strlen(res) - 1);\n        }\n    }\n    return res;\n}\n
iteration.kt
/* Nested for loop */\nfun nestedForLoop(n: Int): String {\n    val res = StringBuilder()\n    // Loop i = 1, 2, ..., n-1, n\n    for (i in 1..n) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (j in 1..n) {\n            res.append(\" ($i, $j), \")\n        }\n    }\n    return res.toString()\n}\n
iteration.rb
### Nested for loop ###\ndef nested_for_loop(n)\n  res = \"\"\n\n  # Loop i = 1, 2, ..., n-1, n\n  for i in 1..n\n    # Loop j = 1, 2, ..., n-1, n\n    for j in 1..n\n      res += \"(#{i}, #{j}), \"\n    end\n  end\n\n  res\nend\n

Figure 2-2 shows the flowchart of this nested loop.

Figure 2-2   Flowchart of nested loops

In this case, the number of operations of the function is proportional to \\(n^2\\), or the algorithm's running time has a \"quadratic relationship\" with the input data size \\(n\\).

We can continue adding nested loops, where each nesting is a \"dimension increase\", raising the time complexity to \"cubic relationship\", \"quartic relationship\", and so on.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#222-recursion","level":2,"title":"2.2.2   Recursion","text":"

Recursion is an algorithmic strategy that solves problems by having a function call itself. It mainly consists of two phases.

  1. Descend: The program continuously calls itself deeper, usually passing in smaller or more simplified parameters, until reaching a \"termination condition\".
  2. Ascend: After triggering the \"termination condition\", the program returns layer by layer from the deepest recursive function, aggregating the result of each layer.

From an implementation perspective, recursive code mainly consists of three elements.

  1. Termination condition: Used to determine when to switch from \"descending\" to \"ascending\".
  2. Recursive call: Corresponds to \"descending\", where the function calls itself, usually with smaller or more simplified parameters.
  3. Return result: Corresponds to \"ascending\", returning the result of the current recursion level to the previous layer.

Observe the following code. We only need to call the function recur(n) to complete the calculation of \\(1 + 2 + \\dots + n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
def recur(n: int) -> int:\n    \"\"\"Recursion\"\"\"\n    # Termination condition\n    if n == 1:\n        return 1\n    # Recurse: recursive call\n    res = recur(n - 1)\n    # Return: return result\n    return n + res\n
recursion.cpp
/* Recursion */\nint recur(int n) {\n    // Termination condition\n    if (n == 1)\n        return 1;\n    // Recurse: recursive call\n    int res = recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.java
/* Recursion */\nint recur(int n) {\n    // Termination condition\n    if (n == 1)\n        return 1;\n    // Recurse: recursive call\n    int res = recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.cs
/* Recursion */\nint Recur(int n) {\n    // Termination condition\n    if (n == 1)\n        return 1;\n    // Recurse: recursive call\n    int res = Recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.go
/* Recursion */\nfunc recur(n int) int {\n    // Termination condition\n    if n == 1 {\n        return 1\n    }\n    // Recurse: recursive call\n    res := recur(n - 1)\n    // Return: return result\n    return n + res\n}\n
recursion.swift
/* Recursion */\nfunc recur(n: Int) -> Int {\n    // Termination condition\n    if n == 1 {\n        return 1\n    }\n    // Recurse: recursive call\n    let res = recur(n: n - 1)\n    // Return: return result\n    return n + res\n}\n
recursion.js
/* Recursion */\nfunction recur(n) {\n    // Termination condition\n    if (n === 1) return 1;\n    // Recurse: recursive call\n    const res = recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.ts
/* Recursion */\nfunction recur(n: number): number {\n    // Termination condition\n    if (n === 1) return 1;\n    // Recurse: recursive call\n    const res = recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.dart
/* Recursion */\nint recur(int n) {\n  // Termination condition\n  if (n == 1) return 1;\n  // Recurse: recursive call\n  int res = recur(n - 1);\n  // Return: return result\n  return n + res;\n}\n
recursion.rs
/* Recursion */\nfn recur(n: i32) -> i32 {\n    // Termination condition\n    if n == 1 {\n        return 1;\n    }\n    // Recurse: recursive call\n    let res = recur(n - 1);\n    // Return: return result\n    n + res\n}\n
recursion.c
/* Recursion */\nint recur(int n) {\n    // Termination condition\n    if (n == 1)\n        return 1;\n    // Recurse: recursive call\n    int res = recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.kt
/* Recursion */\nfun recur(n: Int): Int {\n    // Termination condition\n    if (n == 1)\n        return 1\n    // Descend: recursive call\n    val res = recur(n - 1)\n    // Return: return result\n    return n + res\n}\n
recursion.rb
### Recursion ###\ndef recur(n)\n  # Termination condition\n  return 1 if n == 1\n  # Recurse: recursive call\n  res = recur(n - 1)\n  # Return: return result\n  n + res\nend\n

Figure 2-3 shows the recursive process of this function.

Figure 2-3   Recursive process of the summation function

Although from a computational perspective, iteration and recursion can achieve the same results, they represent two completely different paradigms for thinking about and solving problems.

  • Iteration: Solves problems \"bottom-up\". Starting from the most basic steps, these steps are then repeatedly executed or accumulated until the task is complete.
  • Recursion: Solves problems \"top-down\". The original problem is decomposed into smaller subproblems that have the same form as the original problem. These subproblems continue to be decomposed into even smaller subproblems until reaching the base case (where the solution is known).

Taking the above summation function as an example, let the problem be \\(f(n) = 1 + 2 + \\dots + n\\).

  • Iteration: Simulates the summation process in a loop, traversing from \\(1\\) to \\(n\\), performing the summation operation in each round to obtain \\(f(n)\\).
  • Recursion: Decomposes the problem into the subproblem \\(f(n) = n + f(n-1)\\), continuously decomposing (recursively) until terminating at the base case \\(f(1) = 1\\).
","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#1-call-stack","level":3,"title":"1.   Call Stack","text":"

Each time a recursive function calls itself, the system allocates memory for the newly opened function to store local variables, call addresses, and other information. This leads to two consequences.

  • The function's context data is stored in a memory area called \"stack frame space\", which is not released until the function returns. Therefore, recursion usually consumes more memory space than iteration.
  • Recursive function calls incur additional overhead. Therefore, recursion is usually less time-efficient than loops.

As shown in Figure 2-4, before the termination condition is triggered, there are \\(n\\) unreturned recursive functions existing simultaneously, with a recursion depth of \\(n\\).

Figure 2-4   Recursion call depth

In practice, the recursion depth allowed by programming languages is usually limited, and excessively deep recursion may lead to stack overflow errors.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-tail-recursion","level":3,"title":"2.   Tail Recursion","text":"

Interestingly, if a function makes the recursive call as the very last step before returning, the function can be optimized by the compiler or interpreter to have space efficiency comparable to iteration. This case is called tail recursion.

  • Regular recursion: When a function returns to the previous level, it needs to continue executing code, so the system needs to save the context of the previous layer's call.
  • Tail recursion: The recursive call is the last operation before the function returns, meaning that after returning to the previous level, there is no need to continue executing other operations, so the system does not need to save the context of the previous layer's function.

Taking the calculation of \\(1 + 2 + \\dots + n\\) as an example, we can set the result variable res as a function parameter to implement tail recursion:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
def tail_recur(n, res):\n    \"\"\"Tail recursion\"\"\"\n    # Termination condition\n    if n == 0:\n        return res\n    # Tail recursive call\n    return tail_recur(n - 1, res + n)\n
recursion.cpp
/* Tail recursion */\nint tailRecur(int n, int res) {\n    // Termination condition\n    if (n == 0)\n        return res;\n    // Tail recursive call\n    return tailRecur(n - 1, res + n);\n}\n
recursion.java
/* Tail recursion */\nint tailRecur(int n, int res) {\n    // Termination condition\n    if (n == 0)\n        return res;\n    // Tail recursive call\n    return tailRecur(n - 1, res + n);\n}\n
recursion.cs
/* Tail recursion */\nint TailRecur(int n, int res) {\n    // Termination condition\n    if (n == 0)\n        return res;\n    // Tail recursive call\n    return TailRecur(n - 1, res + n);\n}\n
recursion.go
/* Tail recursion */\nfunc tailRecur(n int, res int) int {\n    // Termination condition\n    if n == 0 {\n        return res\n    }\n    // Tail recursive call\n    return tailRecur(n-1, res+n)\n}\n
recursion.swift
/* Tail recursion */\nfunc tailRecur(n: Int, res: Int) -> Int {\n    // Termination condition\n    if n == 0 {\n        return res\n    }\n    // Tail recursive call\n    return tailRecur(n: n - 1, res: res + n)\n}\n
recursion.js
/* Tail recursion */\nfunction tailRecur(n, res) {\n    // Termination condition\n    if (n === 0) return res;\n    // Tail recursive call\n    return tailRecur(n - 1, res + n);\n}\n
recursion.ts
/* Tail recursion */\nfunction tailRecur(n: number, res: number): number {\n    // Termination condition\n    if (n === 0) return res;\n    // Tail recursive call\n    return tailRecur(n - 1, res + n);\n}\n
recursion.dart
/* Tail recursion */\nint tailRecur(int n, int res) {\n  // Termination condition\n  if (n == 0) return res;\n  // Tail recursive call\n  return tailRecur(n - 1, res + n);\n}\n
recursion.rs
/* Tail recursion */\nfn tail_recur(n: i32, res: i32) -> i32 {\n    // Termination condition\n    if n == 0 {\n        return res;\n    }\n    // Tail recursive call\n    tail_recur(n - 1, res + n)\n}\n
recursion.c
/* Tail recursion */\nint tailRecur(int n, int res) {\n    // Termination condition\n    if (n == 0)\n        return res;\n    // Tail recursive call\n    return tailRecur(n - 1, res + n);\n}\n
recursion.kt
/* Tail recursion */\ntailrec fun tailRecur(n: Int, res: Int): Int {\n    // Add tailrec keyword to enable tail recursion optimization\n    // Termination condition\n    if (n == 0)\n        return res\n    // Tail recursive call\n    return tailRecur(n - 1, res + n)\n}\n
recursion.rb
### Tail recursion ###\ndef tail_recur(n, res)\n  # Termination condition\n  return res if n == 0\n  # Tail recursive call\n  tail_recur(n - 1, res + n)\nend\n

The execution process of tail recursion is shown in Figure 2-5. Comparing regular recursion and tail recursion, the execution point of the summation operation is different.

  • Regular recursion: The summation operation is performed during the \"ascending\" process, requiring an additional summation operation after each layer returns.
  • Tail recursion: The summation operation is performed during the \"descending\" process; the \"ascending\" process only needs to return layer by layer.

Figure 2-5   Tail recursion process

Tip

Please note that many compilers or interpreters do not support tail recursion optimization. For example, Python does not support tail recursion optimization by default, so even if a function is in tail recursive form, it may still encounter stack overflow issues.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#3-recursion-tree","level":3,"title":"3.   Recursion Tree","text":"

When dealing with algorithmic problems related to \"divide and conquer\", recursion often provides a more intuitive approach and more readable code than iteration. Taking the \"Fibonacci sequence\" as an example.

Question

Given a Fibonacci sequence \\(0, 1, 1, 2, 3, 5, 8, 13, \\dots\\), find the \\(n\\)-th number in the sequence.

Let the \\(n\\)-th number of the Fibonacci sequence be \\(f(n)\\). Two conclusions can be easily obtained.

  • The first two numbers of the sequence are \\(f(1) = 0\\) and \\(f(2) = 1\\).
  • Each number in the sequence is the sum of the previous two numbers, i.e., \\(f(n) = f(n - 1) + f(n - 2)\\).

Following the recurrence relation to make recursive calls, with the first two numbers as termination conditions, we can write the recursive code. Calling fib(n) will give us the \\(n\\)-th number of the Fibonacci sequence:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
def fib(n: int) -> int:\n    \"\"\"Fibonacci sequence: recursion\"\"\"\n    # Termination condition f(1) = 0, f(2) = 1\n    if n == 1 or n == 2:\n        return n - 1\n    # Recursive call f(n) = f(n-1) + f(n-2)\n    res = fib(n - 1) + fib(n - 2)\n    # Return result f(n)\n    return res\n
recursion.cpp
/* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.java
/* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.cs
/* Fibonacci sequence: recursion */\nint Fib(int n) {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    int res = Fib(n - 1) + Fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.go
/* Fibonacci sequence: recursion */\nfunc fib(n int) int {\n    // Termination condition f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1\n    }\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    res := fib(n-1) + fib(n-2)\n    // Return result f(n)\n    return res\n}\n
recursion.swift
/* Fibonacci sequence: recursion */\nfunc fib(n: Int) -> Int {\n    // Termination condition f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1\n    }\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    let res = fib(n: n - 1) + fib(n: n - 2)\n    // Return result f(n)\n    return res\n}\n
recursion.js
/* Fibonacci sequence: recursion */\nfunction fib(n) {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n === 1 || n === 2) return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    const res = fib(n - 1) + fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.ts
/* Fibonacci sequence: recursion */\nfunction fib(n: number): number {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n === 1 || n === 2) return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    const res = fib(n - 1) + fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.dart
/* Fibonacci sequence: recursion */\nint fib(int n) {\n  // Termination condition f(1) = 0, f(2) = 1\n  if (n == 1 || n == 2) return n - 1;\n  // Recursive call f(n) = f(n-1) + f(n-2)\n  int res = fib(n - 1) + fib(n - 2);\n  // Return result f(n)\n  return res;\n}\n
recursion.rs
/* Fibonacci sequence: recursion */\nfn fib(n: i32) -> i32 {\n    // Termination condition f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1;\n    }\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    let res = fib(n - 1) + fib(n - 2);\n    // Return result\n    res\n}\n
recursion.c
/* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.kt
/* Fibonacci sequence: recursion */\nfun fib(n: Int): Int {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    val res = fib(n - 1) + fib(n - 2)\n    // Return result f(n)\n    return res\n}\n
recursion.rb
### Fibonacci sequence: recursion ###\ndef fib(n)\n  # Termination condition f(1) = 0, f(2) = 1\n  return n - 1 if n == 1 || n == 2\n  # Recursive call f(n) = f(n-1) + f(n-2)\n  res = fib(n - 1) + fib(n - 2)\n  # Return result f(n)\n  res\nend\n

Observing the above code, we recursively call two functions within the function, meaning that one call produces two call branches. As shown in Figure 2-6, such continuous recursive calling will eventually produce a recursion tree with \\(n\\) levels.

Figure 2-6   Recursion tree of the Fibonacci sequence

Fundamentally, recursion embodies the paradigm of \"decomposing a problem into smaller subproblems\", and this divide-and-conquer strategy is crucial.

  • From an algorithmic perspective, many important algorithmic strategies such as searching, sorting, backtracking, divide and conquer, and dynamic programming directly or indirectly apply this way of thinking.
  • From a data structure perspective, recursion is naturally suited for handling problems related to linked lists, trees, and graphs, because they are well-suited for analysis using divide-and-conquer thinking.
","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#223-comparison-of-the-two","level":2,"title":"2.2.3   Comparison of the Two","text":"

Summarizing the above content, as shown in Table 2-1, iteration and recursion differ in implementation, performance, and applicability.

Table 2-1   Comparison of iteration and recursion characteristics

Iteration Recursion Implementation Loop structure Function calls itself Time efficiency Generally more efficient, no function call overhead Each function call incurs overhead Memory usage Usually uses a fixed amount of memory space Accumulated function calls may use a large amount of stack frame space Suitable problems Suitable for simple loop tasks, with intuitive and readable code Suitable for subproblem decomposition, such as trees, graphs, divide and conquer, backtracking, etc., with concise and clear code structure

Tip

If you find the following content difficult to understand, you can review it after reading the \"Stack\" chapter.

What is the intrinsic relationship between iteration and recursion? Taking the above recursive function as an example, the summation operation is performed during the \"ascending\" phase of recursion. This means that the function called first actually completes its summation operation last, and this working mechanism is similar to the \"last-in, first-out\" principle of stacks.

In fact, recursive terminology such as \"call stack\" and \"stack frame space\" already hints at the close relationship between recursion and stacks.

  1. Descend: When a function is called, the system allocates a new stack frame on the \"call stack\" for that function to store the function's local variables, parameters, return address, and other data.
  2. Ascend: When the function completes execution and returns, the corresponding stack frame is removed from the \"call stack\", restoring the execution environment of the previous function.

Therefore, we can use an explicit stack to simulate the behavior of the call stack, thus transforming recursion into iterative form:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
def for_loop_recur(n: int) -> int:\n    \"\"\"Simulate recursion using iteration\"\"\"\n    # Use an explicit stack to simulate the system call stack\n    stack = []\n    res = 0\n    # Recurse: recursive call\n    for i in range(n, 0, -1):\n        # Simulate \"recurse\" with \"push\"\n        stack.append(i)\n    # Return: return result\n    while stack:\n        # Simulate \"return\" with \"pop\"\n        res += stack.pop()\n    # res = 1+2+3+...+n\n    return res\n
recursion.cpp
/* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    stack<int> stack;\n    int res = 0;\n    // Recurse: recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i);\n    }\n    // Return: return result\n    while (!stack.empty()) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.top();\n        stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.java
/* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    Stack<Integer> stack = new Stack<>();\n    int res = 0;\n    // Recurse: recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i);\n    }\n    // Return: return result\n    while (!stack.isEmpty()) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.cs
/* Simulate recursion using iteration */\nint ForLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    Stack<int> stack = new();\n    int res = 0;\n    // Recurse: recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack.Push(i);\n    }\n    // Return: return result\n    while (stack.Count > 0) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.Pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.go
/* Simulate recursion using iteration */\nfunc forLoopRecur(n int) int {\n    // Use an explicit stack to simulate the system call stack\n    stack := list.New()\n    res := 0\n    // Recurse: recursive call\n    for i := n; i > 0; i-- {\n        // Simulate \"recurse\" with \"push\"\n        stack.PushBack(i)\n    }\n    // Return: return result\n    for stack.Len() != 0 {\n        // Simulate \"return\" with \"pop\"\n        res += stack.Back().Value.(int)\n        stack.Remove(stack.Back())\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
recursion.swift
/* Simulate recursion using iteration */\nfunc forLoopRecur(n: Int) -> Int {\n    // Use an explicit stack to simulate the system call stack\n    var stack: [Int] = []\n    var res = 0\n    // Recurse: recursive call\n    for i in (1 ... n).reversed() {\n        // Simulate \"recurse\" with \"push\"\n        stack.append(i)\n    }\n    // Return: return result\n    while !stack.isEmpty {\n        // Simulate \"return\" with \"pop\"\n        res += stack.removeLast()\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
recursion.js
/* Simulate recursion using iteration */\nfunction forLoopRecur(n) {\n    // Use an explicit stack to simulate the system call stack\n    const stack = [];\n    let res = 0;\n    // Recurse: recursive call\n    for (let i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i);\n    }\n    // Return: return result\n    while (stack.length) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.ts
/* Simulate recursion using iteration */\nfunction forLoopRecur(n: number): number {\n    // Use an explicit stack to simulate the system call stack\n    const stack: number[] = [];\n    let res: number = 0;\n    // Recurse: recursive call\n    for (let i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i);\n    }\n    // Return: return result\n    while (stack.length) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.dart
/* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n  // Use an explicit stack to simulate the system call stack\n  List<int> stack = [];\n  int res = 0;\n  // Recurse: recursive call\n  for (int i = n; i > 0; i--) {\n    // Simulate \"recurse\" with \"push\"\n    stack.add(i);\n  }\n  // Return: return result\n  while (!stack.isEmpty) {\n    // Simulate \"return\" with \"pop\"\n    res += stack.removeLast();\n  }\n  // res = 1+2+3+...+n\n  return res;\n}\n
recursion.rs
/* Simulate recursion using iteration */\nfn for_loop_recur(n: i32) -> i32 {\n    // Use an explicit stack to simulate the system call stack\n    let mut stack = Vec::new();\n    let mut res = 0;\n    // Recurse: recursive call\n    for i in (1..=n).rev() {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i);\n    }\n    // Return: return result\n    while !stack.is_empty() {\n        // Simulate \"return\" with \"pop\"\n        res += stack.pop().unwrap();\n    }\n    // res = 1+2+3+...+n\n    res\n}\n
recursion.c
/* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    int stack[1000]; // Use a large array to simulate stack\n    int top = -1;    // Stack top index\n    int res = 0;\n    // Recurse: recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack[1 + top++] = i;\n    }\n    // Return: return result\n    while (top >= 0) {\n        // Simulate \"return\" with \"pop\"\n        res += stack[top--];\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.kt
/* Simulate recursion using iteration */\nfun forLoopRecur(n: Int): Int {\n    // Use an explicit stack to simulate the system call stack\n    val stack = Stack<Int>()\n    var res = 0\n    // Descend: recursive call\n    for (i in n downTo 0) {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i)\n    }\n    // Return: return result\n    while (stack.isNotEmpty()) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.pop()\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
recursion.rb
### Use iteration to simulate recursion ###\ndef for_loop_recur(n)\n  # Use an explicit stack to simulate the system call stack\n  stack = []\n  res = 0\n\n  # Recurse: recursive call\n  for i in n.downto(0)\n    # Simulate \"recurse\" with \"push\"\n    stack << i\n  end\n  # Return: return result\n  while !stack.empty?\n    res += stack.pop\n  end\n\n  # res = 1+2+3+...+n\n  res\nend\n

Observing the above code, when recursion is transformed into iteration, the code becomes more complex. Although iteration and recursion can be converted into each other in many cases, it may not be worthwhile to do so for the following two reasons.

  • The transformed code may be more difficult to understand and less readable.
  • For some complex problems, simulating the behavior of the system call stack can be very difficult.

In summary, choosing between iteration and recursion depends on the nature of the specific problem. In programming practice, it is crucial to weigh the pros and cons of both and choose the appropriate method based on the context.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/","level":1,"title":"2.1   Algorithm Efficiency Evaluation","text":"

In algorithm design, we pursue the following two levels of objectives sequentially.

  1. Finding a solution to the problem: The algorithm must reliably obtain the correct solution within the specified input range.
  2. Seeking the optimal solution: Multiple solutions may exist for the same problem, and we hope to find an algorithm that is as efficient as possible.

In other words, under the premise of being able to solve the problem, algorithm efficiency has become the primary evaluation criterion for measuring the quality of algorithms. It includes the following two dimensions.

  • Time efficiency: The length of time the algorithm runs.
  • Space efficiency: The size of memory space the algorithm occupies.

In short, our goal is to design data structures and algorithms that are \"both fast and memory-efficient\". Effectively evaluating algorithm efficiency is crucial, because only in this way can we compare various algorithms and guide the algorithm design and optimization process.

Efficiency evaluation methods are mainly divided into two types: actual testing and theoretical estimation.

","path":["Chapter 2. Complexity Analysis","2.1   Algorithm Efficiency Evaluation"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/#211-actual-testing","level":2,"title":"2.1.1   Actual Testing","text":"

Suppose we now have algorithm A and algorithm B, both of which can solve the same problem, and we need to compare the efficiency of these two algorithms. The most direct method is to find a computer, run these two algorithms, and monitor and record their running time and memory usage. This evaluation approach can reflect the real situation, but it also has considerable limitations.

On one hand, it is difficult to eliminate interference factors from the testing environment. Hardware configuration affects the performance of algorithms. For example, if an algorithm has a high degree of parallelism, it is more suitable for running on multi-core CPUs; if an algorithm has intensive memory operations, it will perform better on high-performance memory. In other words, the test results of an algorithm on different machines may be inconsistent. This means we need to test on various machines and calculate average efficiency, which is impractical.

On the other hand, conducting complete testing is very resource-intensive. As the input data volume changes, the algorithm will exhibit different efficiencies. For example, when the input data volume is small, the running time of algorithm A is shorter than algorithm B; but when the input data volume is large, the test results may be exactly the opposite. Therefore, to obtain convincing conclusions, we need to test input data of various scales, which requires a large amount of computational resources.

","path":["Chapter 2. Complexity Analysis","2.1   Algorithm Efficiency Evaluation"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/#212-theoretical-estimation","level":2,"title":"2.1.2   Theoretical Estimation","text":"

Since actual testing has considerable limitations, we can consider evaluating algorithm efficiency through calculations alone. This estimation method is called asymptotic complexity analysis, or complexity analysis for short.

Complexity analysis can reflect the relationship between the time and space resources required for algorithm execution and the input data scale. It describes the growth trend of the time and space required for algorithm execution as the input data scale increases. This definition is somewhat convoluted, so we can break it down into three key points to understand.

  • \"Time and space resources\" correspond to time complexity and space complexity, respectively.
  • \"As the input data scale increases\" means that complexity reflects the relationship between algorithm running efficiency and input data scale.
  • \"Growth trend of time and space\" indicates that complexity analysis focuses not on the specific values of running time or occupied space, but on how \"fast\" time or space grows.

Complexity analysis overcomes the drawbacks of the actual testing method, reflected in the following aspects.

  • It does not need to actually run the code, making it more environmentally friendly and energy-efficient.
  • It is independent of the testing environment, and the analysis results are applicable to all running platforms.
  • It can reflect algorithm efficiency at different data volumes, especially algorithm performance at large data volumes.

Tip

If you are still confused about the concept of complexity, don't worry—we will introduce it in detail in subsequent chapters.

Complexity analysis provides us with a \"ruler\" for evaluating algorithm efficiency, allowing us to measure the time and space resources required to execute a certain algorithm and compare the efficiency between different algorithms.

Complexity is a mathematical concept that may be relatively abstract for beginners, with a relatively high learning difficulty. From this perspective, complexity analysis may not be very suitable as the first content to be introduced. However, when we discuss the characteristics of a certain data structure or algorithm, it is difficult to avoid analyzing its running speed and space usage.

In summary, it is recommended that before diving deep into data structures and algorithms, you first establish a preliminary understanding of complexity analysis so that you can complete complexity analysis of simple algorithms.

","path":["Chapter 2. Complexity Analysis","2.1   Algorithm Efficiency Evaluation"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/","level":1,"title":"2.4   Space Complexity","text":"

Space complexity measures the growth trend of memory space occupied by an algorithm as the data size increases. This concept is very similar to time complexity, except that \"running time\" is replaced with \"occupied memory space\".

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#241-algorithm-related-space","level":2,"title":"2.4.1   Algorithm-Related Space","text":"

The memory space used by an algorithm during execution mainly includes the following types.

  • Input space: Used to store the input data of the algorithm.
  • Temporary space: Used to store variables, objects, function contexts, and other data during the algorithm's execution.
  • Output space: Used to store the output data of the algorithm.

In general, the scope of space complexity statistics is \"temporary space\" plus \"output space\".

Temporary space can be further divided into three parts.

  • Temporary data: Used to save various constants, variables, objects, etc., during the algorithm's execution.
  • Stack frame space: Used to save the context data of called functions. The system creates a stack frame at the top of the stack each time a function is called, and the stack frame space is released after the function returns.
  • Instruction space: Used to save compiled program instructions, which are usually ignored in actual statistics.

When analyzing the space complexity of a program, we usually count three parts: temporary data, stack frame space, and output data, as shown in the following figure.

Figure 2-15   Algorithm-related space

The related code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
class Node:\n    \"\"\"Class\"\"\"\n    def __init__(self, x: int):\n        self.val: int = x              # Node value\n        self.next: Node | None = None  # Reference to the next node\n\ndef function() -> int:\n    \"\"\"Function\"\"\"\n    # Perform some operations...\n    return 0\n\ndef algorithm(n) -> int:  # Input data\n    A = 0                 # Temporary data (constant, usually represented by uppercase letters)\n    b = 0                 # Temporary data (variable)\n    node = Node(0)        # Temporary data (object)\n    c = function()        # Stack frame space (function call)\n    return A + b + c      # Output data\n
/* Structure */\nstruct Node {\n    int val;\n    Node *next;\n    Node(int x) : val(x), next(nullptr) {}\n};\n\n/* Function */\nint func() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) {        // Input data\n    const int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node* node = new Node(0); // Temporary data (object)\n    int c = func();           // Stack frame space (function call)\n    return a + b + c;         // Output data\n}\n
/* Class */\nclass Node {\n    int val;\n    Node next;\n    Node(int x) { val = x; }\n}\n\n/* Function */\nint function() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) {        // Input data\n    final int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node node = new Node(0);  // Temporary data (object)\n    int c = function();       // Stack frame space (function call)\n    return a + b + c;         // Output data\n}\n
/* Class */\nclass Node(int x) {\n    int val = x;\n    Node next;\n}\n\n/* Function */\nint Function() {\n    // Perform some operations...\n    return 0;\n}\n\nint Algorithm(int n) {        // Input data\n    const int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node node = new(0);       // Temporary data (object)\n    int c = Function();       // Stack frame space (function call)\n    return a + b + c;         // Output data\n}\n
/* Structure */\ntype node struct {\n    val  int\n    next *node\n}\n\n/* Create node structure */\nfunc newNode(val int) *node {\n    return &node{val: val}\n}\n\n/* Function */\nfunc function() int {\n    // Perform some operations...\n    return 0\n}\n\nfunc algorithm(n int) int { // Input data\n    const a = 0             // Temporary data (constant)\n    b := 0                  // Temporary data (variable)\n    newNode(0)              // Temporary data (object)\n    c := function()         // Stack frame space (function call)\n    return a + b + c        // Output data\n}\n
/* Class */\nclass Node {\n    var val: Int\n    var next: Node?\n\n    init(x: Int) {\n        val = x\n    }\n}\n\n/* Function */\nfunc function() -> Int {\n    // Perform some operations...\n    return 0\n}\n\nfunc algorithm(n: Int) -> Int { // Input data\n    let a = 0             // Temporary data (constant)\n    var b = 0             // Temporary data (variable)\n    let node = Node(x: 0) // Temporary data (object)\n    let c = function()    // Stack frame space (function call)\n    return a + b + c      // Output data\n}\n
/* Class */\nclass Node {\n    val;\n    next;\n    constructor(val) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.next = null;                       // Reference to the next node\n    }\n}\n\n/* Function */\nfunction constFunc() {\n    // Perform some operations\n    return 0;\n}\n\nfunction algorithm(n) {       // Input data\n    const a = 0;              // Temporary data (constant)\n    let b = 0;                // Temporary data (variable)\n    const node = new Node(0); // Temporary data (object)\n    const c = constFunc();    // Stack frame space (function call)\n    return a + b + c;         // Output data\n}\n
/* Class */\nclass Node {\n    val: number;\n    next: Node | null;\n    constructor(val?: number) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.next = null;                       // Reference to the next node\n    }\n}\n\n/* Function */\nfunction constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n\nfunction algorithm(n: number): number { // Input data\n    const a = 0;                        // Temporary data (constant)\n    let b = 0;                          // Temporary data (variable)\n    const node = new Node(0);           // Temporary data (object)\n    const c = constFunc();              // Stack frame space (function call)\n    return a + b + c;                   // Output data\n}\n
/* Class */\nclass Node {\n  int val;\n  Node next;\n  Node(this.val, [this.next]);\n}\n\n/* Function */\nint function() {\n  // Perform some operations...\n  return 0;\n}\n\nint algorithm(int n) {  // Input data\n  const int a = 0;      // Temporary data (constant)\n  int b = 0;            // Temporary data (variable)\n  Node node = Node(0);  // Temporary data (object)\n  int c = function();   // Stack frame space (function call)\n  return a + b + c;     // Output data\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Structure */\nstruct Node {\n    val: i32,\n    next: Option<Rc<RefCell<Node>>>,\n}\n\n/* Create Node structure */\nimpl Node {\n    fn new(val: i32) -> Self {\n        Self { val: val, next: None }\n    }\n}\n\n/* Function */\nfn function() -> i32 {\n    // Perform some operations...\n    return 0;\n}\n\nfn algorithm(n: i32) -> i32 {       // Input data\n    const a: i32 = 0;               // Temporary data (constant)\n    let mut b = 0;                  // Temporary data (variable)\n    let node = Node::new(0);        // Temporary data (object)\n    let c = function();             // Stack frame space (function call)\n    return a + b + c;               // Output data\n}\n
/* Function */\nint func() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) { // Input data\n    const int a = 0;   // Temporary data (constant)\n    int b = 0;         // Temporary data (variable)\n    int c = func();    // Stack frame space (function call)\n    return a + b + c;  // Output data\n}\n
/* Class */\nclass Node(var _val: Int) {\n    var next: Node? = null\n}\n\n/* Function */\nfun function(): Int {\n    // Perform some operations...\n    return 0\n}\n\nfun algorithm(n: Int): Int { // Input data\n    val a = 0                // Temporary data (constant)\n    var b = 0                // Temporary data (variable)\n    val node = Node(0)       // Temporary data (object)\n    val c = function()       // Stack frame space (function call)\n    return a + b + c         // Output data\n}\n
### Class ###\nclass Node\n    attr_accessor :val      # Node value\n    attr_accessor :next     # Reference to the next node\n\n    def initialize(x)\n        @val = x\n    end\nend\n\n### Function ###\ndef function\n    # Perform some operations...\n    0\nend\n\n### Algorithm ###\ndef algorithm(n)        # Input data\n    a = 0               # Temporary data (constant)\n    b = 0               # Temporary data (variable)\n    node = Node.new(0)  # Temporary data (object)\n    c = function        # Stack frame space (function call)\n    a + b + c           # Output data\nend\n
","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#242-calculation-method","level":2,"title":"2.4.2   Calculation Method","text":"

The calculation method for space complexity is roughly the same as for time complexity, except that the statistical object is changed from \"number of operations\" to \"size of space used\".

Unlike time complexity, we usually only focus on the worst-case space complexity. This is because memory space is a hard requirement, and we must ensure that sufficient memory space is reserved for all input data.

Observe the following code. The \"worst case\" in worst-case space complexity has two meanings.

  1. Based on the worst input data: When \\(n < 10\\), the space complexity is \\(O(1)\\); but when \\(n > 10\\), the initialized array nums occupies \\(O(n)\\) space, so the worst-case space complexity is \\(O(n)\\).
  2. Based on the peak memory during algorithm execution: For example, before executing the last line, the program occupies \\(O(1)\\) space; when initializing the array nums, the program occupies \\(O(n)\\) space, so the worst-case space complexity is \\(O(n)\\).
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
def algorithm(n: int):\n    a = 0               # O(1)\n    b = [0] * 10000     # O(1)\n    if n > 10:\n        nums = [0] * n  # O(n)\n
void algorithm(int n) {\n    int a = 0;               // O(1)\n    vector<int> b(10000);    // O(1)\n    if (n > 10)\n        vector<int> nums(n); // O(n)\n}\n
void algorithm(int n) {\n    int a = 0;                   // O(1)\n    int[] b = new int[10000];    // O(1)\n    if (n > 10)\n        int[] nums = new int[n]; // O(n)\n}\n
void Algorithm(int n) {\n    int a = 0;                   // O(1)\n    int[] b = new int[10000];    // O(1)\n    if (n > 10) {\n        int[] nums = new int[n]; // O(n)\n    }\n}\n
func algorithm(n int) {\n    a := 0                      // O(1)\n    b := make([]int, 10000)     // O(1)\n    var nums []int\n    if n > 10 {\n        nums := make([]int, n)  // O(n)\n    }\n    fmt.Println(a, b, nums)\n}\n
func algorithm(n: Int) {\n    let a = 0 // O(1)\n    let b = Array(repeating: 0, count: 10000) // O(1)\n    if n > 10 {\n        let nums = Array(repeating: 0, count: n) // O(n)\n    }\n}\n
function algorithm(n) {\n    const a = 0;                   // O(1)\n    const b = new Array(10000);    // O(1)\n    if (n > 10) {\n        const nums = new Array(n); // O(n)\n    }\n}\n
function algorithm(n: number): void {\n    const a = 0;                   // O(1)\n    const b = new Array(10000);    // O(1)\n    if (n > 10) {\n        const nums = new Array(n); // O(n)\n    }\n}\n
void algorithm(int n) {\n  int a = 0;                            // O(1)\n  List<int> b = List.filled(10000, 0);  // O(1)\n  if (n > 10) {\n    List<int> nums = List.filled(n, 0); // O(n)\n  }\n}\n
fn algorithm(n: i32) {\n    let a = 0;                              // O(1)\n    let b = [0; 10000];                     // O(1)\n    if n > 10 {\n        let nums = vec![0; n as usize];     // O(n)\n    }\n}\n
void algorithm(int n) {\n    int a = 0;               // O(1)\n    int b[10000];            // O(1)\n    if (n > 10)\n        int nums[n] = {0};   // O(n)\n}\n
fun algorithm(n: Int) {\n    val a = 0                    // O(1)\n    val b = IntArray(10000)      // O(1)\n    if (n > 10) {\n        val nums = IntArray(n)   // O(n)\n    }\n}\n
def algorithm(n)\n    a = 0                           # O(1)\n    b = Array.new(10000)            # O(1)\n    nums = Array.new(n) if n > 10   # O(n)\nend\n

In recursive functions, it is necessary to count the stack frame space. Observe the following code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
def function() -> int:\n    # Perform some operations\n    return 0\n\ndef loop(n: int):\n    \"\"\"Loop has space complexity of O(1)\"\"\"\n    for _ in range(n):\n        function()\n\ndef recur(n: int):\n    \"\"\"Recursion has space complexity of O(n)\"\"\"\n    if n == 1:\n        return\n    return recur(n - 1)\n
int func() {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n/* Recursion has space complexity of O(n) */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
int function() {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        function();\n    }\n}\n/* Recursion has space complexity of O(n) */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
int Function() {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nvoid Loop(int n) {\n    for (int i = 0; i < n; i++) {\n        Function();\n    }\n}\n/* Recursion has space complexity of O(n) */\nint Recur(int n) {\n    if (n == 1) return 1;\n    return Recur(n - 1);\n}\n
func function() int {\n    // Perform some operations\n    return 0\n}\n\n/* Loop has space complexity of O(1) */\nfunc loop(n int) {\n    for i := 0; i < n; i++ {\n        function()\n    }\n}\n\n/* Recursion has space complexity of O(n) */\nfunc recur(n int) {\n    if n == 1 {\n        return\n    }\n    recur(n - 1)\n}\n
@discardableResult\nfunc function() -> Int {\n    // Perform some operations\n    return 0\n}\n\n/* Loop has space complexity of O(1) */\nfunc loop(n: Int) {\n    for _ in 0 ..< n {\n        function()\n    }\n}\n\n/* Recursion has space complexity of O(n) */\nfunc recur(n: Int) {\n    if n == 1 {\n        return\n    }\n    recur(n: n - 1)\n}\n
function constFunc() {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nfunction loop(n) {\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n/* Recursion has space complexity of O(n) */\nfunction recur(n) {\n    if (n === 1) return;\n    return recur(n - 1);\n}\n
function constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nfunction loop(n: number): void {\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n/* Recursion has space complexity of O(n) */\nfunction recur(n: number): void {\n    if (n === 1) return;\n    return recur(n - 1);\n}\n
int function() {\n  // Perform some operations\n  return 0;\n}\n/* Loop has space complexity of O(1) */\nvoid loop(int n) {\n  for (int i = 0; i < n; i++) {\n    function();\n  }\n}\n/* Recursion has space complexity of O(n) */\nvoid recur(int n) {\n  if (n == 1) return;\n  recur(n - 1);\n}\n
fn function() -> i32 {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nfn loop(n: i32) {\n    for i in 0..n {\n        function();\n    }\n}\n/* Recursion has space complexity of O(n) */\nfn recur(n: i32) {\n    if n == 1 {\n        return;\n    }\n    recur(n - 1);\n}\n
int func() {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n/* Recursion has space complexity of O(n) */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
fun function(): Int {\n    // Perform some operations\n    return 0\n}\n/* Loop has space complexity of O(1) */\nfun loop(n: Int) {\n    for (i in 0..<n) {\n        function()\n    }\n}\n/* Recursion has space complexity of O(n) */\nfun recur(n: Int) {\n    if (n == 1) return\n    return recur(n - 1)\n}\n
def function\n    # Perform some operations\n    0\nend\n\n### Loop has space complexity of O(1) ###\ndef loop(n)\n    (0...n).each { function }\nend\n\n### Recursion has space complexity of O(n) ###\ndef recur(n)\n    return if n == 1\n    recur(n - 1)\nend\n

The time complexity of both functions loop() and recur() is \\(O(n)\\), but their space complexities are different.

  • The function loop() calls function() \\(n\\) times in a loop. In each iteration, function() returns and releases its stack frame space, so the space complexity remains \\(O(1)\\).
  • The recursive function recur() has \\(n\\) unreturned recur() instances existing simultaneously during execution, thus occupying \\(O(n)\\) stack frame space.
","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#243-common-types","level":2,"title":"2.4.3   Common Types","text":"

Let the input data size be \\(n\\). The following figure shows common types of space complexity (arranged from low to high).

\\[ \\begin{aligned} O(1) < O(\\log n) < O(n) < O(n^2) < O(2^n) \\newline \\text{Constant} < \\text{Logarithmic} < \\text{Linear} < \\text{Quadratic} < \\text{Exponential} \\end{aligned} \\]

Figure 2-16   Common types of space complexity

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#1-constant-order-o1","level":3,"title":"1.   Constant Order \\(O(1)\\)","text":"

Constant order is common in constants, variables, and objects whose quantity is independent of the input data size \\(n\\).

It should be noted that memory occupied by initializing variables or calling functions in a loop is released when entering the next iteration, so it does not accumulate space, and the space complexity remains \\(O(1)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def function() -> int:\n    \"\"\"Function\"\"\"\n    # Perform some operations\n    return 0\n\ndef constant(n: int):\n    \"\"\"Constant order\"\"\"\n    # Constants, variables, objects occupy O(1) space\n    a = 0\n    nums = [0] * 10000\n    node = ListNode(0)\n    # Variables in the loop occupy O(1) space\n    for _ in range(n):\n        c = 0\n    # Functions in the loop occupy O(1) space\n    for _ in range(n):\n        function()\n
space_complexity.cpp
/* Function */\nint func() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nvoid constant(int n) {\n    // Constants, variables, objects occupy O(1) space\n    const int a = 0;\n    int b = 0;\n    vector<int> nums(10000);\n    ListNode node(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n
space_complexity.java
/* Function */\nint function() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nvoid constant(int n) {\n    // Constants, variables, objects occupy O(1) space\n    final int a = 0;\n    int b = 0;\n    int[] nums = new int[10000];\n    ListNode node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        function();\n    }\n}\n
space_complexity.cs
/* Function */\nint Function() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nvoid Constant(int n) {\n    // Constants, variables, objects occupy O(1) space\n    int a = 0;\n    int b = 0;\n    int[] nums = new int[10000];\n    ListNode node = new(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        Function();\n    }\n}\n
space_complexity.go
/* Function */\nfunc function() int {\n    // Perform some operations...\n    return 0\n}\n\n/* Constant order */\nfunc spaceConstant(n int) {\n    // Constants, variables, objects occupy O(1) space\n    const a = 0\n    b := 0\n    nums := make([]int, 10000)\n    node := newNode(0)\n    // Variables in the loop occupy O(1) space\n    var c int\n    for i := 0; i < n; i++ {\n        c = 0\n    }\n    // Functions in the loop occupy O(1) space\n    for i := 0; i < n; i++ {\n        function()\n    }\n    b += 0\n    c += 0\n    nums[0] = 0\n    node.val = 0\n}\n
space_complexity.swift
/* Function */\n@discardableResult\nfunc function() -> Int {\n    // Perform some operations\n    return 0\n}\n\n/* Constant order */\nfunc constant(n: Int) {\n    // Constants, variables, objects occupy O(1) space\n    let a = 0\n    var b = 0\n    let nums = Array(repeating: 0, count: 10000)\n    let node = ListNode(x: 0)\n    // Variables in the loop occupy O(1) space\n    for _ in 0 ..< n {\n        let c = 0\n    }\n    // Functions in the loop occupy O(1) space\n    for _ in 0 ..< n {\n        function()\n    }\n}\n
space_complexity.js
/* Function */\nfunction constFunc() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nfunction constant(n) {\n    // Constants, variables, objects occupy O(1) space\n    const a = 0;\n    const b = 0;\n    const nums = new Array(10000);\n    const node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        const c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n
space_complexity.ts
/* Function */\nfunction constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nfunction constant(n: number): void {\n    // Constants, variables, objects occupy O(1) space\n    const a = 0;\n    const b = 0;\n    const nums = new Array(10000);\n    const node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        const c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n
space_complexity.dart
/* Function */\nint function() {\n  // Perform some operations\n  return 0;\n}\n\n/* Constant order */\nvoid constant(int n) {\n  // Constants, variables, objects occupy O(1) space\n  final int a = 0;\n  int b = 0;\n  List<int> nums = List.filled(10000, 0);\n  ListNode node = ListNode(0);\n  // Variables in the loop occupy O(1) space\n  for (var i = 0; i < n; i++) {\n    int c = 0;\n  }\n  // Functions in the loop occupy O(1) space\n  for (var i = 0; i < n; i++) {\n    function();\n  }\n}\n
space_complexity.rs
/* Function */\nfn function() -> i32 {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\n#[allow(unused)]\nfn constant(n: i32) {\n    // Constants, variables, objects occupy O(1) space\n    const A: i32 = 0;\n    let b = 0;\n    let nums = vec![0; 10000];\n    let node = ListNode::new(0);\n    // Variables in the loop occupy O(1) space\n    for i in 0..n {\n        let c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for i in 0..n {\n        function();\n    }\n}\n
space_complexity.c
/* Function */\nint func() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nvoid constant(int n) {\n    // Constants, variables, objects occupy O(1) space\n    const int a = 0;\n    int b = 0;\n    int nums[1000];\n    ListNode *node = newListNode(0);\n    free(node);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n
space_complexity.kt
/* Function */\nfun function(): Int {\n    // Perform some operations\n    return 0\n}\n\n/* Constant order */\nfun constant(n: Int) {\n    // Constants, variables, objects occupy O(1) space\n    val a = 0\n    var b = 0\n    val nums = Array(10000) { 0 }\n    val node = ListNode(0)\n    // Variables in the loop occupy O(1) space\n    for (i in 0..<n) {\n        val c = 0\n    }\n    // Functions in the loop occupy O(1) space\n    for (i in 0..<n) {\n        function()\n    }\n}\n
space_complexity.rb
### Function ###\ndef function\n  # Perform some operations\n  0\nend\n\n### Constant time ###\ndef constant(n)\n  # Constants, variables, objects occupy O(1) space\n  a = 0\n  nums = [0] * 10000\n  node = ListNode.new\n\n  # Variables in the loop occupy O(1) space\n  (0...n).each { c = 0 }\n  # Functions in the loop occupy O(1) space\n  (0...n).each { function }\nend\n
","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#2-linear-order-on","level":3,"title":"2.   Linear Order \\(O(n)\\)","text":"

Linear order is common in arrays, linked lists, stacks, queues, etc., where the number of elements is proportional to \\(n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def linear(n: int):\n    \"\"\"Linear order\"\"\"\n    # A list of length n occupies O(n) space\n    nums = [0] * n\n    # A hash table of length n occupies O(n) space\n    hmap = dict[int, str]()\n    for i in range(n):\n        hmap[i] = str(i)\n
space_complexity.cpp
/* Linear order */\nvoid linear(int n) {\n    // Array of length n uses O(n) space\n    vector<int> nums(n);\n    // A list of length n occupies O(n) space\n    vector<ListNode> nodes;\n    for (int i = 0; i < n; i++) {\n        nodes.push_back(ListNode(i));\n    }\n    // A hash table of length n occupies O(n) space\n    unordered_map<int, string> map;\n    for (int i = 0; i < n; i++) {\n        map[i] = to_string(i);\n    }\n}\n
space_complexity.java
/* Linear order */\nvoid linear(int n) {\n    // Array of length n uses O(n) space\n    int[] nums = new int[n];\n    // A list of length n occupies O(n) space\n    List<ListNode> nodes = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        nodes.add(new ListNode(i));\n    }\n    // A hash table of length n occupies O(n) space\n    Map<Integer, String> map = new HashMap<>();\n    for (int i = 0; i < n; i++) {\n        map.put(i, String.valueOf(i));\n    }\n}\n
space_complexity.cs
/* Linear order */\nvoid Linear(int n) {\n    // Array of length n uses O(n) space\n    int[] nums = new int[n];\n    // A list of length n occupies O(n) space\n    List<ListNode> nodes = [];\n    for (int i = 0; i < n; i++) {\n        nodes.Add(new ListNode(i));\n    }\n    // A hash table of length n occupies O(n) space\n    Dictionary<int, string> map = [];\n    for (int i = 0; i < n; i++) {\n        map.Add(i, i.ToString());\n    }\n}\n
space_complexity.go
/* Linear order */\nfunc spaceLinear(n int) {\n    // Array of length n uses O(n) space\n    _ = make([]int, n)\n    // A list of length n occupies O(n) space\n    var nodes []*node\n    for i := 0; i < n; i++ {\n        nodes = append(nodes, newNode(i))\n    }\n    // A hash table of length n occupies O(n) space\n    m := make(map[int]string, n)\n    for i := 0; i < n; i++ {\n        m[i] = strconv.Itoa(i)\n    }\n}\n
space_complexity.swift
/* Linear order */\nfunc linear(n: Int) {\n    // Array of length n uses O(n) space\n    let nums = Array(repeating: 0, count: n)\n    // A list of length n occupies O(n) space\n    let nodes = (0 ..< n).map { ListNode(x: $0) }\n    // A hash table of length n occupies O(n) space\n    let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, \"\\($0)\") })\n}\n
space_complexity.js
/* Linear order */\nfunction linear(n) {\n    // Array of length n uses O(n) space\n    const nums = new Array(n);\n    // A list of length n occupies O(n) space\n    const nodes = [];\n    for (let i = 0; i < n; i++) {\n        nodes.push(new ListNode(i));\n    }\n    // A hash table of length n occupies O(n) space\n    const map = new Map();\n    for (let i = 0; i < n; i++) {\n        map.set(i, i.toString());\n    }\n}\n
space_complexity.ts
/* Linear order */\nfunction linear(n: number): void {\n    // Array of length n uses O(n) space\n    const nums = new Array(n);\n    // A list of length n occupies O(n) space\n    const nodes: ListNode[] = [];\n    for (let i = 0; i < n; i++) {\n        nodes.push(new ListNode(i));\n    }\n    // A hash table of length n occupies O(n) space\n    const map = new Map();\n    for (let i = 0; i < n; i++) {\n        map.set(i, i.toString());\n    }\n}\n
space_complexity.dart
/* Linear order */\nvoid linear(int n) {\n  // Array of length n uses O(n) space\n  List<int> nums = List.filled(n, 0);\n  // A list of length n occupies O(n) space\n  List<ListNode> nodes = [];\n  for (var i = 0; i < n; i++) {\n    nodes.add(ListNode(i));\n  }\n  // A hash table of length n occupies O(n) space\n  Map<int, String> map = HashMap();\n  for (var i = 0; i < n; i++) {\n    map.putIfAbsent(i, () => i.toString());\n  }\n}\n
space_complexity.rs
/* Linear order */\n#[allow(unused)]\nfn linear(n: i32) {\n    // Array of length n uses O(n) space\n    let mut nums = vec![0; n as usize];\n    // A list of length n occupies O(n) space\n    let mut nodes = Vec::new();\n    for i in 0..n {\n        nodes.push(ListNode::new(i))\n    }\n    // A hash table of length n occupies O(n) space\n    let mut map = HashMap::new();\n    for i in 0..n {\n        map.insert(i, i.to_string());\n    }\n}\n
space_complexity.c
/* Hash table */\ntypedef struct {\n    int key;\n    int val;\n    UT_hash_handle hh; // Implemented using uthash.h\n} HashTable;\n\n/* Linear order */\nvoid linear(int n) {\n    // Array of length n uses O(n) space\n    int *nums = malloc(sizeof(int) * n);\n    free(nums);\n\n    // A list of length n occupies O(n) space\n    ListNode **nodes = malloc(sizeof(ListNode *) * n);\n    for (int i = 0; i < n; i++) {\n        nodes[i] = newListNode(i);\n    }\n    // Memory release\n    for (int i = 0; i < n; i++) {\n        free(nodes[i]);\n    }\n    free(nodes);\n\n    // A hash table of length n occupies O(n) space\n    HashTable *h = NULL;\n    for (int i = 0; i < n; i++) {\n        HashTable *tmp = malloc(sizeof(HashTable));\n        tmp->key = i;\n        tmp->val = i;\n        HASH_ADD_INT(h, key, tmp);\n    }\n\n    // Memory release\n    HashTable *curr, *tmp;\n    HASH_ITER(hh, h, curr, tmp) {\n        HASH_DEL(h, curr);\n        free(curr);\n    }\n}\n
space_complexity.kt
/* Linear order */\nfun linear(n: Int) {\n    // Array of length n uses O(n) space\n    val nums = Array(n) { 0 }\n    // A list of length n occupies O(n) space\n    val nodes = mutableListOf<ListNode>()\n    for (i in 0..<n) {\n        nodes.add(ListNode(i))\n    }\n    // A hash table of length n occupies O(n) space\n    val map = mutableMapOf<Int, String>()\n    for (i in 0..<n) {\n        map[i] = i.toString()\n    }\n}\n
space_complexity.rb
### Linear time ###\ndef linear(n)\n  # A list of length n occupies O(n) space\n  nums = Array.new(n, 0)\n\n  # A hash table of length n occupies O(n) space\n  hmap = {}\n  for i in 0...n\n    hmap[i] = i.to_s\n  end\nend\n

As shown in the following figure, the recursion depth of this function is \\(n\\), meaning that there are \\(n\\) unreturned linear_recur() functions existing simultaneously, using \\(O(n)\\) stack frame space:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def linear_recur(n: int):\n    \"\"\"Linear order (recursive implementation)\"\"\"\n    print(\"Recursion n =\", n)\n    if n == 1:\n        return\n    linear_recur(n - 1)\n
space_complexity.cpp
/* Linear order (recursive implementation) */\nvoid linearRecur(int n) {\n    cout << \"Recursion n = \" << n << endl;\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
space_complexity.java
/* Linear order (recursive implementation) */\nvoid linearRecur(int n) {\n    System.out.println(\"Recursion n = \" + n);\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
space_complexity.cs
/* Linear order (recursive implementation) */\nvoid LinearRecur(int n) {\n    Console.WriteLine(\"Recursion n = \" + n);\n    if (n == 1) return;\n    LinearRecur(n - 1);\n}\n
space_complexity.go
/* Linear order (recursive implementation) */\nfunc spaceLinearRecur(n int) {\n    fmt.Println(\"Recursion n =\", n)\n    if n == 1 {\n        return\n    }\n    spaceLinearRecur(n - 1)\n}\n
space_complexity.swift
/* Linear order (recursive implementation) */\nfunc linearRecur(n: Int) {\n    print(\"Recursion n = \\(n)\")\n    if n == 1 {\n        return\n    }\n    linearRecur(n: n - 1)\n}\n
space_complexity.js
/* Linear order (recursive implementation) */\nfunction linearRecur(n) {\n    console.log(`Recursion n = ${n}`);\n    if (n === 1) return;\n    linearRecur(n - 1);\n}\n
space_complexity.ts
/* Linear order (recursive implementation) */\nfunction linearRecur(n: number): void {\n    console.log(`Recursion n = ${n}`);\n    if (n === 1) return;\n    linearRecur(n - 1);\n}\n
space_complexity.dart
/* Linear order (recursive implementation) */\nvoid linearRecur(int n) {\n  print('Recursion n = $n');\n  if (n == 1) return;\n  linearRecur(n - 1);\n}\n
space_complexity.rs
/* Linear order (recursive implementation) */\nfn linear_recur(n: i32) {\n    println!(\"Recursion n = {}\", n);\n    if n == 1 {\n        return;\n    };\n    linear_recur(n - 1);\n}\n
space_complexity.c
/* Linear order (recursive implementation) */\nvoid linearRecur(int n) {\n    printf(\"Recursion n = %d\\r\\n\", n);\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
space_complexity.kt
/* Linear order (recursive implementation) */\nfun linearRecur(n: Int) {\n    println(\"Recursion n = $n\")\n    if (n == 1)\n        return\n    linearRecur(n - 1)\n}\n
space_complexity.rb
### Linear space (recursive) ###\ndef linear_recur(n)\n  puts \"Recursion n = #{n}\"\n  return if n == 1\n  linear_recur(n - 1)\nend\n

Figure 2-17   Linear order space complexity generated by recursive function

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#3-quadratic-order-on2","level":3,"title":"3.   Quadratic Order \\(O(n^2)\\)","text":"

Quadratic order is common in matrices and graphs, where the number of elements is quadratically related to \\(n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def quadratic(n: int):\n    \"\"\"Quadratic order\"\"\"\n    # A 2D list occupies O(n^2) space\n    num_matrix = [[0] * n for _ in range(n)]\n
space_complexity.cpp
/* Exponential order */\nvoid quadratic(int n) {\n    // 2D list uses O(n^2) space\n    vector<vector<int>> numMatrix;\n    for (int i = 0; i < n; i++) {\n        vector<int> tmp;\n        for (int j = 0; j < n; j++) {\n            tmp.push_back(0);\n        }\n        numMatrix.push_back(tmp);\n    }\n}\n
space_complexity.java
/* Exponential order */\nvoid quadratic(int n) {\n    // Matrix uses O(n^2) space\n    int[][] numMatrix = new int[n][n];\n    // 2D list uses O(n^2) space\n    List<List<Integer>> numList = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        List<Integer> tmp = new ArrayList<>();\n        for (int j = 0; j < n; j++) {\n            tmp.add(0);\n        }\n        numList.add(tmp);\n    }\n}\n
space_complexity.cs
/* Exponential order */\nvoid Quadratic(int n) {\n    // Matrix uses O(n^2) space\n    int[,] numMatrix = new int[n, n];\n    // 2D list uses O(n^2) space\n    List<List<int>> numList = [];\n    for (int i = 0; i < n; i++) {\n        List<int> tmp = [];\n        for (int j = 0; j < n; j++) {\n            tmp.Add(0);\n        }\n        numList.Add(tmp);\n    }\n}\n
space_complexity.go
/* Exponential order */\nfunc spaceQuadratic(n int) {\n    // Matrix uses O(n^2) space\n    numMatrix := make([][]int, n)\n    for i := 0; i < n; i++ {\n        numMatrix[i] = make([]int, n)\n    }\n}\n
space_complexity.swift
/* Exponential order */\nfunc quadratic(n: Int) {\n    // 2D list uses O(n^2) space\n    let numList = Array(repeating: Array(repeating: 0, count: n), count: n)\n}\n
space_complexity.js
/* Exponential order */\nfunction quadratic(n) {\n    // Matrix uses O(n^2) space\n    const numMatrix = Array(n)\n        .fill(null)\n        .map(() => Array(n).fill(null));\n    // 2D list uses O(n^2) space\n    const numList = [];\n    for (let i = 0; i < n; i++) {\n        const tmp = [];\n        for (let j = 0; j < n; j++) {\n            tmp.push(0);\n        }\n        numList.push(tmp);\n    }\n}\n
space_complexity.ts
/* Exponential order */\nfunction quadratic(n: number): void {\n    // Matrix uses O(n^2) space\n    const numMatrix = Array(n)\n        .fill(null)\n        .map(() => Array(n).fill(null));\n    // 2D list uses O(n^2) space\n    const numList = [];\n    for (let i = 0; i < n; i++) {\n        const tmp = [];\n        for (let j = 0; j < n; j++) {\n            tmp.push(0);\n        }\n        numList.push(tmp);\n    }\n}\n
space_complexity.dart
/* Exponential order */\nvoid quadratic(int n) {\n  // Matrix uses O(n^2) space\n  List<List<int>> numMatrix = List.generate(n, (_) => List.filled(n, 0));\n  // 2D list uses O(n^2) space\n  List<List<int>> numList = [];\n  for (var i = 0; i < n; i++) {\n    List<int> tmp = [];\n    for (int j = 0; j < n; j++) {\n      tmp.add(0);\n    }\n    numList.add(tmp);\n  }\n}\n
space_complexity.rs
/* Exponential order */\n#[allow(unused)]\nfn quadratic(n: i32) {\n    // Matrix uses O(n^2) space\n    let num_matrix = vec![vec![0; n as usize]; n as usize];\n    // 2D list uses O(n^2) space\n    let mut num_list = Vec::new();\n    for i in 0..n {\n        let mut tmp = Vec::new();\n        for j in 0..n {\n            tmp.push(0);\n        }\n        num_list.push(tmp);\n    }\n}\n
space_complexity.c
/* Exponential order */\nvoid quadratic(int n) {\n    // 2D list uses O(n^2) space\n    int **numMatrix = malloc(sizeof(int *) * n);\n    for (int i = 0; i < n; i++) {\n        int *tmp = malloc(sizeof(int) * n);\n        for (int j = 0; j < n; j++) {\n            tmp[j] = 0;\n        }\n        numMatrix[i] = tmp;\n    }\n\n    // Memory release\n    for (int i = 0; i < n; i++) {\n        free(numMatrix[i]);\n    }\n    free(numMatrix);\n}\n
space_complexity.kt
/* Exponential order */\nfun quadratic(n: Int) {\n    // Matrix uses O(n^2) space\n    val numMatrix = arrayOfNulls<Array<Int>?>(n)\n    // 2D list uses O(n^2) space\n    val numList = mutableListOf<MutableList<Int>>()\n    for (i in 0..<n) {\n        val tmp = mutableListOf<Int>()\n        for (j in 0..<n) {\n            tmp.add(0)\n        }\n        numList.add(tmp)\n    }\n}\n
space_complexity.rb
### Quadratic time ###\ndef quadratic(n)\n  # 2D list uses O(n^2) space\n  Array.new(n) { Array.new(n, 0) }\nend\n

As shown in the following figure, the recursion depth of this function is \\(n\\), and an array is initialized in each recursive function with lengths of \\(n\\), \\(n-1\\), \\(\\dots\\), \\(2\\), \\(1\\), with an average length of \\(n / 2\\), thus occupying \\(O(n^2)\\) space overall:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def quadratic_recur(n: int) -> int:\n    \"\"\"Quadratic order (recursive implementation)\"\"\"\n    if n <= 0:\n        return 0\n    # Array nums length is n, n-1, ..., 2, 1\n    nums = [0] * n\n    return quadratic_recur(n - 1)\n
space_complexity.cpp
/* Quadratic order (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    vector<int> nums(n);\n    cout << \"In recursion n = \" << n << \", nums length = \" << nums.size() << endl;\n    return quadraticRecur(n - 1);\n}\n
space_complexity.java
/* Quadratic order (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    // Array nums has length n, n-1, ..., 2, 1\n    int[] nums = new int[n];\n    System.out.println(\"In recursion n = \" + n + \", nums length = \" + nums.length);\n    return quadraticRecur(n - 1);\n}\n
space_complexity.cs
/* Quadratic order (recursive implementation) */\nint QuadraticRecur(int n) {\n    if (n <= 0) return 0;\n    int[] nums = new int[n];\n    Console.WriteLine(\"Recursion n = \" + n + \", nums length = \" + nums.Length);\n    return QuadraticRecur(n - 1);\n}\n
space_complexity.go
/* Quadratic order (recursive implementation) */\nfunc spaceQuadraticRecur(n int) int {\n    if n <= 0 {\n        return 0\n    }\n    nums := make([]int, n)\n    fmt.Printf(\"In recursion n = %d, nums length = %d \\n\", n, len(nums))\n    return spaceQuadraticRecur(n - 1)\n}\n
space_complexity.swift
/* Quadratic order (recursive implementation) */\n@discardableResult\nfunc quadraticRecur(n: Int) -> Int {\n    if n <= 0 {\n        return 0\n    }\n    // Array nums has length n, n-1, ..., 2, 1\n    let nums = Array(repeating: 0, count: n)\n    print(\"In recursion n = \\(n), nums length = \\(nums.count)\")\n    return quadraticRecur(n: n - 1)\n}\n
space_complexity.js
/* Quadratic order (recursive implementation) */\nfunction quadraticRecur(n) {\n    if (n <= 0) return 0;\n    const nums = new Array(n);\n    console.log(`In recursion n = ${n}, nums length = ${nums.length}`);\n    return quadraticRecur(n - 1);\n}\n
space_complexity.ts
/* Quadratic order (recursive implementation) */\nfunction quadraticRecur(n: number): number {\n    if (n <= 0) return 0;\n    const nums = new Array(n);\n    console.log(`In recursion n = ${n}, nums length = ${nums.length}`);\n    return quadraticRecur(n - 1);\n}\n
space_complexity.dart
/* Quadratic order (recursive implementation) */\nint quadraticRecur(int n) {\n  if (n <= 0) return 0;\n  List<int> nums = List.filled(n, 0);\n  print('In recursion n = $n, nums length = ${nums.length}');\n  return quadraticRecur(n - 1);\n}\n
space_complexity.rs
/* Quadratic order (recursive implementation) */\nfn quadratic_recur(n: i32) -> i32 {\n    if n <= 0 {\n        return 0;\n    };\n    // Array nums has length n, n-1, ..., 2, 1\n    let nums = vec![0; n as usize];\n    println!(\"In recursion n = {}, nums length = {}\", n, nums.len());\n    return quadratic_recur(n - 1);\n}\n
space_complexity.c
/* Quadratic order (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    int *nums = malloc(sizeof(int) * n);\n    printf(\"In recursion n = %d, nums length = %d\\r\\n\", n, n);\n    int res = quadraticRecur(n - 1);\n    free(nums);\n    return res;\n}\n
space_complexity.kt
/* Quadratic order (recursive implementation) */\ntailrec fun quadraticRecur(n: Int): Int {\n    if (n <= 0)\n        return 0\n    // Array nums has length n, n-1, ..., 2, 1\n    val nums = Array(n) { 0 }\n    println(\"In recursion n = $n, nums length = ${nums.size}\")\n    return quadraticRecur(n - 1)\n}\n
space_complexity.rb
### Quadratic space (recursive) ###\ndef quadratic_recur(n)\n  return 0 unless n > 0\n\n  # Array nums has length n, n-1, ..., 2, 1\n  nums = Array.new(n, 0)\n  quadratic_recur(n - 1)\nend\n

Figure 2-18   Quadratic order space complexity generated by recursive function

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#4-exponential-order-o2n","level":3,"title":"4.   Exponential Order \\(O(2^n)\\)","text":"

Exponential order is common in binary trees. Observe the following figure: a \"full binary tree\" with \\(n\\) levels has \\(2^n - 1\\) nodes, occupying \\(O(2^n)\\) space:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def build_tree(n: int) -> TreeNode | None:\n    \"\"\"Exponential order (build full binary tree)\"\"\"\n    if n == 0:\n        return None\n    root = TreeNode(0)\n    root.left = build_tree(n - 1)\n    root.right = build_tree(n - 1)\n    return root\n
space_complexity.cpp
/* Driver Code */\nTreeNode *buildTree(int n) {\n    if (n == 0)\n        return nullptr;\n    TreeNode *root = new TreeNode(0);\n    root->left = buildTree(n - 1);\n    root->right = buildTree(n - 1);\n    return root;\n}\n
space_complexity.java
/* Driver Code */\nTreeNode buildTree(int n) {\n    if (n == 0)\n        return null;\n    TreeNode root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
space_complexity.cs
/* Driver Code */\nTreeNode? BuildTree(int n) {\n    if (n == 0) return null;\n    TreeNode root = new(0) {\n        left = BuildTree(n - 1),\n        right = BuildTree(n - 1)\n    };\n    return root;\n}\n
space_complexity.go
/* Driver Code */\nfunc buildTree(n int) *TreeNode {\n    if n == 0 {\n        return nil\n    }\n    root := NewTreeNode(0)\n    root.Left = buildTree(n - 1)\n    root.Right = buildTree(n - 1)\n    return root\n}\n
space_complexity.swift
/* Driver Code */\nfunc buildTree(n: Int) -> TreeNode? {\n    if n == 0 {\n        return nil\n    }\n    let root = TreeNode(x: 0)\n    root.left = buildTree(n: n - 1)\n    root.right = buildTree(n: n - 1)\n    return root\n}\n
space_complexity.js
/* Driver Code */\nfunction buildTree(n) {\n    if (n === 0) return null;\n    const root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
space_complexity.ts
/* Driver Code */\nfunction buildTree(n: number): TreeNode | null {\n    if (n === 0) return null;\n    const root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
space_complexity.dart
/* Driver Code */\nTreeNode? buildTree(int n) {\n  if (n == 0) return null;\n  TreeNode root = TreeNode(0);\n  root.left = buildTree(n - 1);\n  root.right = buildTree(n - 1);\n  return root;\n}\n
space_complexity.rs
/* Driver Code */\nfn build_tree(n: i32) -> Option<Rc<RefCell<TreeNode>>> {\n    if n == 0 {\n        return None;\n    };\n    let root = TreeNode::new(0);\n    root.borrow_mut().left = build_tree(n - 1);\n    root.borrow_mut().right = build_tree(n - 1);\n    return Some(root);\n}\n
space_complexity.c
/* Driver Code */\nTreeNode *buildTree(int n) {\n    if (n == 0)\n        return NULL;\n    TreeNode *root = newTreeNode(0);\n    root->left = buildTree(n - 1);\n    root->right = buildTree(n - 1);\n    return root;\n}\n
space_complexity.kt
/* Driver Code */\nfun buildTree(n: Int): TreeNode? {\n    if (n == 0)\n        return null\n    val root = TreeNode(0)\n    root.left = buildTree(n - 1)\n    root.right = buildTree(n - 1)\n    return root\n}\n
space_complexity.rb
### Exponential space (build full binary tree) ###\ndef build_tree(n)\n  return if n == 0\n\n  TreeNode.new.tap do |root|\n    root.left = build_tree(n - 1)\n    root.right = build_tree(n - 1)\n  end\nend\n

Figure 2-19   Exponential order space complexity generated by full binary tree

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#5-logarithmic-order-olog-n","level":3,"title":"5.   Logarithmic Order \\(O(\\log n)\\)","text":"

Logarithmic order is common in divide-and-conquer algorithms. For example, merge sort: given an input array of length \\(n\\), each recursion divides the array in half from the midpoint, forming a recursion tree of height \\(\\log n\\), using \\(O(\\log n)\\) stack frame space.

Another example is converting a number to a string. Given a positive integer \\(n\\), it has \\(\\lfloor \\log_{10} n \\rfloor + 1\\) digits, i.e., the corresponding string length is \\(\\lfloor \\log_{10} n \\rfloor + 1\\), so the space complexity is \\(O(\\log_{10} n + 1) = O(\\log n)\\).

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#244-trading-time-for-space","level":2,"title":"2.4.4   Trading Time for Space","text":"

Ideally, we hope that both the time complexity and space complexity of an algorithm can reach optimal. However, in practice, optimizing both time complexity and space complexity simultaneously is usually very difficult.

Reducing time complexity usually comes at the cost of increasing space complexity, and vice versa. The approach of sacrificing memory space to improve algorithm execution speed is called \"trading space for time\"; conversely, it is called \"trading time for space\".

The choice of which approach depends on which aspect we value more. In most cases, time is more precious than space, so \"trading space for time\" is usually the more common strategy. Of course, when the data volume is very large, controlling space complexity is also very important.

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/summary/","level":1,"title":"2.5   Summary","text":"","path":["Chapter 2. Complexity Analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"

Algorithm Efficiency Assessment

  • Time efficiency and space efficiency are the two primary evaluation metrics for measuring algorithm performance.
  • We can evaluate algorithm efficiency through actual testing, but it is difficult to eliminate the influence of the testing environment, and it consumes substantial computational resources.
  • Complexity analysis can eliminate the drawbacks of actual testing, with results applicable to all running platforms, and it can reveal algorithm efficiency under different data scales.

Time Complexity

  • Time complexity is used to measure the trend of algorithm runtime as data volume increases. It can effectively evaluate algorithm efficiency, but may fail in certain situations, such as when the input data volume is small or when time complexities are identical, making it impossible to precisely compare algorithm efficiency.
  • Worst-case time complexity is represented using Big \\(O\\) notation, corresponding to the asymptotic upper bound of a function, reflecting the growth level of the number of operations \\(T(n)\\) as \\(n\\) approaches positive infinity.
  • Deriving time complexity involves two steps: first, counting the number of operations, then determining the asymptotic upper bound.
  • Common time complexities arranged from low to high include \\(O(1)\\), \\(O(\\log n)\\), \\(O(n)\\), \\(O(n \\log n)\\), \\(O(n^2)\\), \\(O(2^n)\\), and \\(O(n!)\\).
  • The time complexity of some algorithms is not fixed, but rather depends on the distribution of input data. Time complexity is divided into worst-case, best-case, and average-case time complexity. Best-case time complexity is rarely used because input data generally needs to satisfy strict conditions to achieve the best case.
  • Average time complexity reflects the algorithm's runtime efficiency under random data input, and is closest to the algorithm's performance in practical applications. Calculating average time complexity requires statistical analysis of input data distribution and the combined mathematical expectation.

Space Complexity

  • Space complexity serves a similar purpose to time complexity, used to measure the trend of algorithm memory usage as data volume increases.
  • The memory space related to algorithm execution can be divided into input space, temporary space, and output space. Typically, input space is not included in space complexity calculations. Temporary space can be divided into temporary data, stack frame space, and instruction space, where stack frame space usually affects space complexity only in recursive functions.
  • We typically only focus on worst-case space complexity, which is the space complexity of an algorithm under worst-case input data and worst-case runtime.
  • Common space complexities arranged from low to high include \\(O(1)\\), \\(O(\\log n)\\), \\(O(n)\\), \\(O(n^2)\\), and \\(O(2^n)\\).
","path":["Chapter 2. Complexity Analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Is the space complexity of tail recursion \\(O(1)\\)?

Theoretically, the space complexity of tail recursive functions can be optimized to \\(O(1)\\). However, most programming languages (such as Java, Python, C++, Go, C#, etc.) do not support automatic tail recursion optimization, so the space complexity is generally considered to be \\(O(n)\\).

Q: What is the difference between the terms function and method?

A function can be executed independently, with all parameters passed explicitly. A method is associated with an object, is implicitly passed to the object that invokes it, and can operate on data contained in class instances.

The following examples use several common programming languages for illustration.

  • C is a procedural programming language without object-oriented concepts, so it only has functions. However, we can simulate object-oriented programming by creating structures (struct), and functions associated with structures are equivalent to methods in other programming languages.
  • Java and C# are object-oriented programming languages where code blocks (methods) are typically part of a class. Static methods behave like functions because they are bound to the class and cannot access specific instance variables.
  • C++ and Python support both procedural programming (functions) and object-oriented programming (methods).

Q: Does the diagram for \"common space complexity types\" reflect the absolute size of occupied space?

No, the diagram shows space complexity, which reflects growth trends rather than the absolute size of occupied space.

Assuming \\(n = 8\\), you might find that the values of each curve do not correspond to the functions. This is because each curve contains a constant term used to compress the value range into a visually comfortable range.

In practice, because we generally do not know what the \"constant term\" complexity of each method is, we usually cannot select the optimal solution for \\(n = 8\\) based on complexity alone. But for \\(n = 8^5\\), the choice is straightforward, as the growth trend already dominates.

Q: Are there situations where algorithms are designed to sacrifice time (or space) based on actual use cases?

In practical applications, most situations choose to sacrifice space for time. For example, with database indexes, we typically choose to build B+ trees or hash indexes, occupying substantial memory space in exchange for efficient queries of \\(O(\\log n)\\) or even \\(O(1)\\).

In scenarios where space resources are precious, time may be sacrificed for space. For example, in embedded development, device memory is precious, and engineers may forgo using hash tables and choose to use array sequential search to save memory usage, at the cost of slower searches.

","path":["Chapter 2. Complexity Analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/","level":1,"title":"2.3   Time Complexity","text":"

Runtime can intuitively and accurately reflect the efficiency of an algorithm. If we want to accurately estimate the runtime of a piece of code, how should we proceed?

  1. Determine the running platform, including hardware configuration, programming language, system environment, etc., as these factors all affect code execution efficiency.
  2. Evaluate the runtime required for various computational operations, for example, an addition operation + requires 1 ns, a multiplication operation * requires 10 ns, a print operation print() requires 5 ns, etc.
  3. Count all computational operations in the code, and sum the execution times of all operations to obtain the runtime.

For example, in the following code, the input data size is \\(n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
# On a certain running platform\ndef algorithm(n: int):\n    a = 2      # 1 ns\n    a = a + 1  # 1 ns\n    a = a * 2  # 10 ns\n    # Loop n times\n    for _ in range(n):  # 1 ns\n        print(0)        # 5 ns\n
// On a certain running platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Loop n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        cout << 0 << endl;         // 5 ns\n    }\n}\n
// On a certain running platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Loop n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        System.out.println(0);     // 5 ns\n    }\n}\n
// On a certain running platform\nvoid Algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Loop n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        Console.WriteLine(0);      // 5 ns\n    }\n}\n
// On a certain running platform\nfunc algorithm(n int) {\n    a := 2     // 1 ns\n    a = a + 1  // 1 ns\n    a = a * 2  // 10 ns\n    // Loop n times\n    for i := 0; i < n; i++ {  // 1 ns\n        fmt.Println(a)        // 5 ns\n    }\n}\n
// On a certain running platform\nfunc algorithm(n: Int) {\n    var a = 2 // 1 ns\n    a = a + 1 // 1 ns\n    a = a * 2 // 10 ns\n    // Loop n times\n    for _ in 0 ..< n { // 1 ns\n        print(0) // 5 ns\n    }\n}\n
// On a certain running platform\nfunction algorithm(n) {\n    var a = 2; // 1 ns\n    a = a + 1; // 1 ns\n    a = a * 2; // 10 ns\n    // Loop n times\n    for(let i = 0; i < n; i++) { // 1 ns\n        console.log(0); // 5 ns\n    }\n}\n
// On a certain running platform\nfunction algorithm(n: number): void {\n    var a: number = 2; // 1 ns\n    a = a + 1; // 1 ns\n    a = a * 2; // 10 ns\n    // Loop n times\n    for(let i = 0; i < n; i++) { // 1 ns\n        console.log(0); // 5 ns\n    }\n}\n
// On a certain running platform\nvoid algorithm(int n) {\n  int a = 2; // 1 ns\n  a = a + 1; // 1 ns\n  a = a * 2; // 10 ns\n  // Loop n times\n  for (int i = 0; i < n; i++) { // 1 ns\n    print(0); // 5 ns\n  }\n}\n
// On a certain running platform\nfn algorithm(n: i32) {\n    let mut a = 2;      // 1 ns\n    a = a + 1;          // 1 ns\n    a = a * 2;          // 10 ns\n    // Loop n times\n    for _ in 0..n {     // 1 ns\n        println!(\"{}\", 0);  // 5 ns\n    }\n}\n
// On a certain running platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Loop n times\n    for (int i = 0; i < n; i++) {   // 1 ns\n        printf(\"%d\", 0);            // 5 ns\n    }\n}\n
// On a certain running platform\nfun algorithm(n: Int) {\n    var a = 2 // 1 ns\n    a = a + 1 // 1 ns\n    a = a * 2 // 10 ns\n    // Loop n times\n    for (i in 0..<n) {  // 1 ns\n        println(0)      // 5 ns\n    }\n}\n
# On a certain running platform\ndef algorithm(n)\n    a = 2       # 1 ns\n    a = a + 1   # 1 ns\n    a = a * 2   # 10 ns\n    # Loop n times\n    (0...n).each do # 1 ns\n        puts 0      # 5 ns\n    end\nend\n

According to the above method, the algorithm's runtime can be obtained as \\((6n + 12)\\) ns:

\\[ 1 + 1 + 10 + (1 + 5) \\times n = 6n + 12 \\]

In reality, however, counting an algorithm's runtime is neither reasonable nor realistic. First, we do not want to tie the estimated time to the running platform, because algorithms need to run on various different platforms. Second, it is difficult to know the runtime of each type of operation, which brings great difficulty to the estimation process.

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#231-counting-time-growth-trends","level":2,"title":"2.3.1   Counting Time Growth Trends","text":"

Time complexity analysis does not count the algorithm's runtime, but rather counts the growth trend of the algorithm's runtime as the data volume increases.

The concept of \"time growth trend\" is rather abstract; let us understand it through an example. Suppose the input data size is \\(n\\), and given three algorithms A, B, and C:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
# Time complexity of algorithm A: constant order\ndef algorithm_A(n: int):\n    print(0)\n# Time complexity of algorithm B: linear order\ndef algorithm_B(n: int):\n    for _ in range(n):\n        print(0)\n# Time complexity of algorithm C: constant order\ndef algorithm_C(n: int):\n    for _ in range(1000000):\n        print(0)\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n    cout << 0 << endl;\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        cout << 0 << endl;\n    }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        cout << 0 << endl;\n    }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n    System.out.println(0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        System.out.println(0);\n    }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        System.out.println(0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nvoid AlgorithmA(int n) {\n    Console.WriteLine(0);\n}\n// Time complexity of algorithm B: linear order\nvoid AlgorithmB(int n) {\n    for (int i = 0; i < n; i++) {\n        Console.WriteLine(0);\n    }\n}\n// Time complexity of algorithm C: constant order\nvoid AlgorithmC(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        Console.WriteLine(0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nfunc algorithm_A(n int) {\n    fmt.Println(0)\n}\n// Time complexity of algorithm B: linear order\nfunc algorithm_B(n int) {\n    for i := 0; i < n; i++ {\n        fmt.Println(0)\n    }\n}\n// Time complexity of algorithm C: constant order\nfunc algorithm_C(n int) {\n    for i := 0; i < 1000000; i++ {\n        fmt.Println(0)\n    }\n}\n
// Time complexity of algorithm A: constant order\nfunc algorithmA(n: Int) {\n    print(0)\n}\n\n// Time complexity of algorithm B: linear order\nfunc algorithmB(n: Int) {\n    for _ in 0 ..< n {\n        print(0)\n    }\n}\n\n// Time complexity of algorithm C: constant order\nfunc algorithmC(n: Int) {\n    for _ in 0 ..< 1_000_000 {\n        print(0)\n    }\n}\n
// Time complexity of algorithm A: constant order\nfunction algorithm_A(n) {\n    console.log(0);\n}\n// Time complexity of algorithm B: linear order\nfunction algorithm_B(n) {\n    for (let i = 0; i < n; i++) {\n        console.log(0);\n    }\n}\n// Time complexity of algorithm C: constant order\nfunction algorithm_C(n) {\n    for (let i = 0; i < 1000000; i++) {\n        console.log(0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nfunction algorithm_A(n: number): void {\n    console.log(0);\n}\n// Time complexity of algorithm B: linear order\nfunction algorithm_B(n: number): void {\n    for (let i = 0; i < n; i++) {\n        console.log(0);\n    }\n}\n// Time complexity of algorithm C: constant order\nfunction algorithm_C(n: number): void {\n    for (let i = 0; i < 1000000; i++) {\n        console.log(0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithmA(int n) {\n  print(0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithmB(int n) {\n  for (int i = 0; i < n; i++) {\n    print(0);\n  }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithmC(int n) {\n  for (int i = 0; i < 1000000; i++) {\n    print(0);\n  }\n}\n
// Time complexity of algorithm A: constant order\nfn algorithm_A(n: i32) {\n    println!(\"{}\", 0);\n}\n// Time complexity of algorithm B: linear order\nfn algorithm_B(n: i32) {\n    for _ in 0..n {\n        println!(\"{}\", 0);\n    }\n}\n// Time complexity of algorithm C: constant order\nfn algorithm_C(n: i32) {\n    for _ in 0..1000000 {\n        println!(\"{}\", 0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n    printf(\"%d\", 0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        printf(\"%d\", 0);\n    }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        printf(\"%d\", 0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nfun algoritm_A(n: Int) {\n    println(0)\n}\n// Time complexity of algorithm B: linear order\nfun algorithm_B(n: Int) {\n    for (i in 0..<n){\n        println(0)\n    }\n}\n// Time complexity of algorithm C: constant order\nfun algorithm_C(n: Int) {\n    for (i in 0..<1000000) {\n        println(0)\n    }\n}\n
# Time complexity of algorithm A: constant order\ndef algorithm_A(n)\n    puts 0\nend\n\n# Time complexity of algorithm B: linear order\ndef algorithm_B(n)\n    (0...n).each { puts 0 }\nend\n\n# Time complexity of algorithm C: constant order\ndef algorithm_C(n)\n    (0...1_000_000).each { puts 0 }\nend\n

Figure 2-7 shows the time complexity of the above three algorithm functions.

  • Algorithm A has only \\(1\\) print operation, and the algorithm's runtime does not grow as \\(n\\) increases. We call the time complexity of this algorithm \"constant order\".
  • In algorithm B, the print operation needs to loop \\(n\\) times, and the algorithm's runtime grows linearly as \\(n\\) increases. The time complexity of this algorithm is called \"linear order\".
  • In algorithm C, the print operation needs to loop \\(1000000\\) times. Although the runtime is very long, it is independent of the input data size \\(n\\). Therefore, the time complexity of C is the same as A, still \"constant order\".

Figure 2-7   Time growth trends of algorithms A, B, and C

Compared to directly counting the algorithm's runtime, what are the characteristics of time complexity analysis?

  • Time complexity can effectively evaluate algorithm efficiency. For example, the runtime of algorithm B grows linearly; when \\(n > 1\\) it is slower than algorithm A, and when \\(n > 1000000\\) it is slower than algorithm C. In fact, as long as the input data size \\(n\\) is sufficiently large, an algorithm with \"constant order\" complexity will always be superior to one with \"linear order\" complexity, which is precisely the meaning of time growth trend.
  • The derivation method for time complexity is simpler. Obviously, the running platform and the types of computational operations are both unrelated to the growth trend of the algorithm's runtime. Therefore, in time complexity analysis, we can simply treat the execution time of all computational operations as the same \"unit time\", thus simplifying \"counting computational operation runtime\" to \"counting the number of computational operations\", which greatly reduces the difficulty of estimation.
  • Time complexity also has certain limitations. For example, although algorithms A and C have the same time complexity, their actual runtimes differ significantly. Similarly, although algorithm B has a higher time complexity than C, when the input data size \\(n\\) is small, algorithm B is clearly superior to algorithm C. In such cases, it is often difficult to judge the efficiency of algorithms based solely on time complexity. Of course, despite the above issues, complexity analysis remains the most effective and commonly used method for evaluating algorithm efficiency.
","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#232-asymptotic-upper-bound-of-functions","level":2,"title":"2.3.2   Asymptotic Upper Bound of Functions","text":"

Given a function with input size \\(n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
def algorithm(n: int):\n    a = 1      # +1\n    a = a + 1  # +1\n    a = a * 2  # +1\n    # Loop n times\n    for i in range(n):  # +1\n        print(0)        # +1\n
void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Loop n times\n    for (int i = 0; i < n; i++) { // +1 (i++ is executed each round)\n        cout << 0 << endl;    // +1\n    }\n}\n
void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Loop n times\n    for (int i = 0; i < n; i++) { // +1 (i++ is executed each round)\n        System.out.println(0);    // +1\n    }\n}\n
void Algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Loop n times\n    for (int i = 0; i < n; i++) {   // +1 (i++ is executed each round)\n        Console.WriteLine(0);   // +1\n    }\n}\n
func algorithm(n int) {\n    a := 1      // +1\n    a = a + 1   // +1\n    a = a * 2   // +1\n    // Loop n times\n    for i := 0; i < n; i++ {   // +1\n        fmt.Println(a)         // +1\n    }\n}\n
func algorithm(n: Int) {\n    var a = 1 // +1\n    a = a + 1 // +1\n    a = a * 2 // +1\n    // Loop n times\n    for _ in 0 ..< n { // +1\n        print(0) // +1\n    }\n}\n
function algorithm(n) {\n    var a = 1; // +1\n    a += 1; // +1\n    a *= 2; // +1\n    // Loop n times\n    for(let i = 0; i < n; i++){ // +1 (i++ is executed each round)\n        console.log(0); // +1\n    }\n}\n
function algorithm(n: number): void{\n    var a: number = 1; // +1\n    a += 1; // +1\n    a *= 2; // +1\n    // Loop n times\n    for(let i = 0; i < n; i++){ // +1 (i++ is executed each round)\n        console.log(0); // +1\n    }\n}\n
void algorithm(int n) {\n  int a = 1; // +1\n  a = a + 1; // +1\n  a = a * 2; // +1\n  // Loop n times\n  for (int i = 0; i < n; i++) { // +1 (i++ is executed each round)\n    print(0); // +1\n  }\n}\n
fn algorithm(n: i32) {\n    let mut a = 1;   // +1\n    a = a + 1;      // +1\n    a = a * 2;      // +1\n\n    // Loop n times\n    for _ in 0..n { // +1 (i++ is executed each round)\n        println!(\"{}\", 0); // +1\n    }\n}\n
void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Loop n times\n    for (int i = 0; i < n; i++) {   // +1 (i++ is executed each round)\n        printf(\"%d\", 0);            // +1\n    }\n}\n
fun algorithm(n: Int) {\n    var a = 1 // +1\n    a = a + 1 // +1\n    a = a * 2 // +1\n    // Loop n times\n    for (i in 0..<n) { // +1 (i++ is executed each round)\n        println(0) // +1\n    }\n}\n
def algorithm(n)\n    a = 1       # +1\n    a = a + 1   # +1\n    a = a * 2   # +1\n    # Loop n times\n    (0...n).each do # +1\n        puts 0      # +1\n    end\nend\n

Let the number of operations of the algorithm be a function of the input data size \\(n\\), denoted as \\(T(n)\\). Then the number of operations of the above function is:

\\[ T(n) = 3 + 2n \\]

\\(T(n)\\) is a linear function, indicating that its runtime growth trend is linear, and therefore its time complexity is linear order.

We denote the time complexity of linear order as \\(O(n)\\). This mathematical symbol is called big-\\(O\\) notation, representing the asymptotic upper bound of the function \\(T(n)\\).

Time complexity analysis essentially calculates the asymptotic upper bound of \"the number of operations \\(T(n)\\)\", which has a clear mathematical definition.

Asymptotic upper bound of functions

If there exist positive real numbers \\(c\\) and \\(n_0\\) such that for all \\(n > n_0\\), we have \\(T(n) \\leq c \\cdot f(n)\\), then \\(f(n)\\) can be considered as an asymptotic upper bound of \\(T(n)\\), denoted as \\(T(n) = O(f(n))\\).

As shown in Figure 2-8, calculating the asymptotic upper bound is to find a function \\(f(n)\\) such that when \\(n\\) tends to infinity, \\(T(n)\\) and \\(f(n)\\) are at the same growth level, differing only by a constant coefficient \\(c\\).

Figure 2-8   Asymptotic upper bound of a function

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#233-derivation-method","level":2,"title":"2.3.3   Derivation Method","text":"

The asymptotic upper bound has a bit of mathematical flavor. If you feel you haven't fully understood it, don't worry. We can first master the derivation method, and gradually grasp its mathematical meaning through continuous practice.

According to the definition, after determining \\(f(n)\\), we can obtain the time complexity \\(O(f(n))\\). So how do we determine the asymptotic upper bound \\(f(n)\\)? Overall, it is divided into two steps: first count the number of operations, then determine the asymptotic upper bound.

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#1-step-1-count-the-number-of-operations","level":3,"title":"1.   Step 1: Count the Number of Operations","text":"

For code, count from top to bottom line by line. However, since the constant coefficient \\(c\\) in \\(c \\cdot f(n)\\) above can be of any size, coefficients and constant terms in the number of operations \\(T(n)\\) can all be ignored. According to this principle, the following counting simplification techniques can be summarized.

  1. Ignore constants in \\(T(n)\\). Because they are all independent of \\(n\\), they do not affect time complexity.
  2. Omit all coefficients. For example, looping \\(2n\\) times, \\(5n + 1\\) times, etc., can all be simplified as \\(n\\) times, because the coefficient before \\(n\\) does not affect time complexity.
  3. Use multiplication for nested loops. The total number of operations equals the product of the number of operations in the outer and inner loops, with each layer of loop still able to apply techniques 1. and 2. separately.

Given a function, we can use the above techniques to count the number of operations:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
def algorithm(n: int):\n    a = 1      # +0 (Technique 1)\n    a = a + n  # +0 (Technique 1)\n    # +n (Technique 2)\n    for i in range(5 * n + 1):\n        print(0)\n    # +n*n (Technique 3)\n    for i in range(2 * n):\n        for j in range(n + 1):\n            print(0)\n
void algorithm(int n) {\n    int a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        cout << 0 << endl;\n    }\n    // +n*n (Technique 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            cout << 0 << endl;\n        }\n    }\n}\n
void algorithm(int n) {\n    int a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        System.out.println(0);\n    }\n    // +n*n (Technique 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            System.out.println(0);\n        }\n    }\n}\n
void Algorithm(int n) {\n    int a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        Console.WriteLine(0);\n    }\n    // +n*n (Technique 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            Console.WriteLine(0);\n        }\n    }\n}\n
func algorithm(n int) {\n    a := 1     // +0 (Technique 1)\n    a = a + n  // +0 (Technique 1)\n    // +n (Technique 2)\n    for i := 0; i < 5 * n + 1; i++ {\n        fmt.Println(0)\n    }\n    // +n*n (Technique 3)\n    for i := 0; i < 2 * n; i++ {\n        for j := 0; j < n + 1; j++ {\n            fmt.Println(0)\n        }\n    }\n}\n
func algorithm(n: Int) {\n    var a = 1 // +0 (Technique 1)\n    a = a + n // +0 (Technique 1)\n    // +n (Technique 2)\n    for _ in 0 ..< (5 * n + 1) {\n        print(0)\n    }\n    // +n*n (Technique 3)\n    for _ in 0 ..< (2 * n) {\n        for _ in 0 ..< (n + 1) {\n            print(0)\n        }\n    }\n}\n
function algorithm(n) {\n    let a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (let i = 0; i < 5 * n + 1; i++) {\n        console.log(0);\n    }\n    // +n*n (Technique 3)\n    for (let i = 0; i < 2 * n; i++) {\n        for (let j = 0; j < n + 1; j++) {\n            console.log(0);\n        }\n    }\n}\n
function algorithm(n: number): void {\n    let a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (let i = 0; i < 5 * n + 1; i++) {\n        console.log(0);\n    }\n    // +n*n (Technique 3)\n    for (let i = 0; i < 2 * n; i++) {\n        for (let j = 0; j < n + 1; j++) {\n            console.log(0);\n        }\n    }\n}\n
void algorithm(int n) {\n  int a = 1; // +0 (Technique 1)\n  a = a + n; // +0 (Technique 1)\n  // +n (Technique 2)\n  for (int i = 0; i < 5 * n + 1; i++) {\n    print(0);\n  }\n  // +n*n (Technique 3)\n  for (int i = 0; i < 2 * n; i++) {\n    for (int j = 0; j < n + 1; j++) {\n      print(0);\n    }\n  }\n}\n
fn algorithm(n: i32) {\n    let mut a = 1;     // +0 (Technique 1)\n    a = a + n;        // +0 (Technique 1)\n\n    // +n (Technique 2)\n    for i in 0..(5 * n + 1) {\n        println!(\"{}\", 0);\n    }\n\n    // +n*n (Technique 3)\n    for i in 0..(2 * n) {\n        for j in 0..(n + 1) {\n            println!(\"{}\", 0);\n        }\n    }\n}\n
void algorithm(int n) {\n    int a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        printf(\"%d\", 0);\n    }\n    // +n*n (Technique 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            printf(\"%d\", 0);\n        }\n    }\n}\n
fun algorithm(n: Int) {\n    var a = 1   // +0 (Technique 1)\n    a = a + n   // +0 (Technique 1)\n    // +n (Technique 2)\n    for (i in 0..<5 * n + 1) {\n        println(0)\n    }\n    // +n*n (Technique 3)\n    for (i in 0..<2 * n) {\n        for (j in 0..<n + 1) {\n            println(0)\n        }\n    }\n}\n
def algorithm(n)\n    a = 1       # +0 (Technique 1)\n    a = a + n   # +0 (Technique 1)\n    # +n (Technique 2)\n    (0...(5 * n + 1)).each do { puts 0 }\n    # +n*n (Technique 3)\n    (0...(2 * n)).each do\n        (0...(n + 1)).each do { puts 0 }\n    end\nend\n

The following formula shows the counting results before and after using the above techniques; both derive a time complexity of \\(O(n^2)\\).

\\[ \\begin{aligned} T(n) & = 2n(n + 1) + (5n + 1) + 2 & \\text{Complete count (-.-|||)} \\newline & = 2n^2 + 7n + 3 \\newline T(n) & = n^2 + n & \\text{Simplified count (o.O)} \\end{aligned} \\]","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#2-step-2-determine-the-asymptotic-upper-bound","level":3,"title":"2.   Step 2: Determine the Asymptotic Upper Bound","text":"

Time complexity is determined by the highest-order term in \\(T(n)\\). This is because as \\(n\\) tends to infinity, the highest-order term will play a dominant role, and the influence of other terms can be ignored.

Table 2-2 shows some examples, where some exaggerated values are used to emphasize the conclusion that \"coefficients cannot shake the order\". When \\(n\\) tends to infinity, these constants become insignificant.

Table 2-2   Time complexities corresponding to different numbers of operations

Number of Operations \\(T(n)\\) Time Complexity \\(O(f(n))\\) \\(100000\\) \\(O(1)\\) \\(3n + 2\\) \\(O(n)\\) \\(2n^2 + 3n + 2\\) \\(O(n^2)\\) \\(n^3 + 10000n^2\\) \\(O(n^3)\\) \\(2^n + 10000n^{10000}\\) \\(O(2^n)\\)","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#234-common-types","level":2,"title":"2.3.4   Common Types","text":"

Let the input data size be \\(n\\). Common time complexity types are shown in Figure 2-9 (arranged in order from low to high).

\\[ \\begin{aligned} O(1) < O(\\log n) < O(n) < O(n \\log n) < O(n^2) < O(2^n) < O(n!) \\newline \\text{Constant order} < \\text{Logarithmic order} < \\text{Linear order} < \\text{Linearithmic order} < \\text{Quadratic order} < \\text{Exponential order} < \\text{Factorial order} \\end{aligned} \\]

Figure 2-9   Common time complexity types

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#1-constant-order-o1","level":3,"title":"1.   Constant Order \\(O(1)\\)","text":"

The number of operations in constant order is independent of the input data size \\(n\\), meaning it does not change as \\(n\\) changes.

In the following function, although the number of operations size may be large, since it is independent of the input data size \\(n\\), the time complexity remains \\(O(1)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def constant(n: int) -> int:\n    \"\"\"Constant order\"\"\"\n    count = 0\n    size = 100000\n    for _ in range(size):\n        count += 1\n    return count\n
time_complexity.cpp
/* Constant order */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
time_complexity.java
/* Constant order */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
time_complexity.cs
/* Constant order */\nint Constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
time_complexity.go
/* Constant order */\nfunc constant(n int) int {\n    count := 0\n    size := 100000\n    for i := 0; i < size; i++ {\n        count++\n    }\n    return count\n}\n
time_complexity.swift
/* Constant order */\nfunc constant(n: Int) -> Int {\n    var count = 0\n    let size = 100_000\n    for _ in 0 ..< size {\n        count += 1\n    }\n    return count\n}\n
time_complexity.js
/* Constant order */\nfunction constant(n) {\n    let count = 0;\n    const size = 100000;\n    for (let i = 0; i < size; i++) count++;\n    return count;\n}\n
time_complexity.ts
/* Constant order */\nfunction constant(n: number): number {\n    let count = 0;\n    const size = 100000;\n    for (let i = 0; i < size; i++) count++;\n    return count;\n}\n
time_complexity.dart
/* Constant order */\nint constant(int n) {\n  int count = 0;\n  int size = 100000;\n  for (var i = 0; i < size; i++) {\n    count++;\n  }\n  return count;\n}\n
time_complexity.rs
/* Constant order */\nfn constant(n: i32) -> i32 {\n    _ = n;\n    let mut count = 0;\n    let size = 100_000;\n    for _ in 0..size {\n        count += 1;\n    }\n    count\n}\n
time_complexity.c
/* Constant order */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    int i = 0;\n    for (int i = 0; i < size; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.kt
/* Constant order */\nfun constant(n: Int): Int {\n    var count = 0\n    val size = 100000\n    for (i in 0..<size)\n        count++\n    return count\n}\n
time_complexity.rb
### Constant time ###\ndef constant(n)\n  count = 0\n  size = 100000\n\n  (0...size).each { count += 1 }\n\n  count\nend\n
","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#2-linear-order-on","level":3,"title":"2.   Linear Order \\(O(n)\\)","text":"

The number of operations in linear order grows linearly relative to the input data size \\(n\\). Linear order typically appears in single-layer loops:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def linear(n: int) -> int:\n    \"\"\"Linear order\"\"\"\n    count = 0\n    for _ in range(n):\n        count += 1\n    return count\n
time_complexity.cpp
/* Linear order */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
time_complexity.java
/* Linear order */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
time_complexity.cs
/* Linear order */\nint Linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
time_complexity.go
/* Linear order */\nfunc linear(n int) int {\n    count := 0\n    for i := 0; i < n; i++ {\n        count++\n    }\n    return count\n}\n
time_complexity.swift
/* Linear order */\nfunc linear(n: Int) -> Int {\n    var count = 0\n    for _ in 0 ..< n {\n        count += 1\n    }\n    return count\n}\n
time_complexity.js
/* Linear order */\nfunction linear(n) {\n    let count = 0;\n    for (let i = 0; i < n; i++) count++;\n    return count;\n}\n
time_complexity.ts
/* Linear order */\nfunction linear(n: number): number {\n    let count = 0;\n    for (let i = 0; i < n; i++) count++;\n    return count;\n}\n
time_complexity.dart
/* Linear order */\nint linear(int n) {\n  int count = 0;\n  for (var i = 0; i < n; i++) {\n    count++;\n  }\n  return count;\n}\n
time_complexity.rs
/* Linear order */\nfn linear(n: i32) -> i32 {\n    let mut count = 0;\n    for _ in 0..n {\n        count += 1;\n    }\n    count\n}\n
time_complexity.c
/* Linear order */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.kt
/* Linear order */\nfun linear(n: Int): Int {\n    var count = 0\n    for (i in 0..<n)\n        count++\n    return count\n}\n
time_complexity.rb
### Linear time ###\ndef linear(n)\n  count = 0\n  (0...n).each { count += 1 }\n  count\nend\n

Operations such as traversing arrays and traversing linked lists have a time complexity of \\(O(n)\\), where \\(n\\) is the length of the array or linked list:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def array_traversal(nums: list[int]) -> int:\n    \"\"\"Linear order (traversing array)\"\"\"\n    count = 0\n    # Number of iterations is proportional to the array length\n    for num in nums:\n        count += 1\n    return count\n
time_complexity.cpp
/* Linear order (traversing array) */\nint arrayTraversal(vector<int> &nums) {\n    int count = 0;\n    // Number of iterations is proportional to the array length\n    for (int num : nums) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.java
/* Linear order (traversing array) */\nint arrayTraversal(int[] nums) {\n    int count = 0;\n    // Number of iterations is proportional to the array length\n    for (int num : nums) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.cs
/* Linear order (traversing array) */\nint ArrayTraversal(int[] nums) {\n    int count = 0;\n    // Number of iterations is proportional to the array length\n    foreach (int num in nums) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.go
/* Linear order (traversing array) */\nfunc arrayTraversal(nums []int) int {\n    count := 0\n    // Number of iterations is proportional to the array length\n    for range nums {\n        count++\n    }\n    return count\n}\n
time_complexity.swift
/* Linear order (traversing array) */\nfunc arrayTraversal(nums: [Int]) -> Int {\n    var count = 0\n    // Number of iterations is proportional to the array length\n    for _ in nums {\n        count += 1\n    }\n    return count\n}\n
time_complexity.js
/* Linear order (traversing array) */\nfunction arrayTraversal(nums) {\n    let count = 0;\n    // Number of iterations is proportional to the array length\n    for (let i = 0; i < nums.length; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.ts
/* Linear order (traversing array) */\nfunction arrayTraversal(nums: number[]): number {\n    let count = 0;\n    // Number of iterations is proportional to the array length\n    for (let i = 0; i < nums.length; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.dart
/* Linear order (traversing array) */\nint arrayTraversal(List<int> nums) {\n  int count = 0;\n  // Number of iterations is proportional to the array length\n  for (var _num in nums) {\n    count++;\n  }\n  return count;\n}\n
time_complexity.rs
/* Linear order (traversing array) */\nfn array_traversal(nums: &[i32]) -> i32 {\n    let mut count = 0;\n    // Number of iterations is proportional to the array length\n    for _ in nums {\n        count += 1;\n    }\n    count\n}\n
time_complexity.c
/* Linear order (traversing array) */\nint arrayTraversal(int *nums, int n) {\n    int count = 0;\n    // Number of iterations is proportional to the array length\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.kt
/* Linear order (traversing array) */\nfun arrayTraversal(nums: IntArray): Int {\n    var count = 0\n    // Number of iterations is proportional to the array length\n    for (num in nums) {\n        count++\n    }\n    return count\n}\n
time_complexity.rb
### Linear time (array traversal) ###\ndef array_traversal(nums)\n  count = 0\n\n  # Number of iterations is proportional to the array length\n  for num in nums\n    count += 1\n  end\n\n  count\nend\n

It is worth noting that the input data size \\(n\\) should be determined according to the type of input data. For example, in the first example, the variable \\(n\\) is the input data size; in the second example, the array length \\(n\\) is the data size.

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#3-quadratic-order-on2","level":3,"title":"3.   Quadratic Order \\(O(n^2)\\)","text":"

The number of operations in quadratic order grows quadratically relative to the input data size \\(n\\). Quadratic order typically appears in nested loops, where both the outer and inner loops have a time complexity of \\(O(n)\\), resulting in an overall time complexity of \\(O(n^2)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def quadratic(n: int) -> int:\n    \"\"\"Quadratic order\"\"\"\n    count = 0\n    # Number of iterations is quadratically related to the data size n\n    for i in range(n):\n        for j in range(n):\n            count += 1\n    return count\n
time_complexity.cpp
/* Exponential order */\nint quadratic(int n) {\n    int count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.java
/* Exponential order */\nint quadratic(int n) {\n    int count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.cs
/* Exponential order */\nint Quadratic(int n) {\n    int count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.go
/* Exponential order */\nfunc quadratic(n int) int {\n    count := 0\n    // Number of iterations is quadratically related to the data size n\n    for i := 0; i < n; i++ {\n        for j := 0; j < n; j++ {\n            count++\n        }\n    }\n    return count\n}\n
time_complexity.swift
/* Exponential order */\nfunc quadratic(n: Int) -> Int {\n    var count = 0\n    // Number of iterations is quadratically related to the data size n\n    for _ in 0 ..< n {\n        for _ in 0 ..< n {\n            count += 1\n        }\n    }\n    return count\n}\n
time_complexity.js
/* Exponential order */\nfunction quadratic(n) {\n    let count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.ts
/* Exponential order */\nfunction quadratic(n: number): number {\n    let count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.dart
/* Exponential order */\nint quadratic(int n) {\n  int count = 0;\n  // Number of iterations is quadratically related to the data size n\n  for (int i = 0; i < n; i++) {\n    for (int j = 0; j < n; j++) {\n      count++;\n    }\n  }\n  return count;\n}\n
time_complexity.rs
/* Exponential order */\nfn quadratic(n: i32) -> i32 {\n    let mut count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for _ in 0..n {\n        for _ in 0..n {\n            count += 1;\n        }\n    }\n    count\n}\n
time_complexity.c
/* Exponential order */\nint quadratic(int n) {\n    int count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.kt
/* Exponential order */\nfun quadratic(n: Int): Int {\n    var count = 0\n    // Number of iterations is quadratically related to the data size n\n    for (i in 0..<n) {\n        for (j in 0..<n) {\n            count++\n        }\n    }\n    return count\n}\n
time_complexity.rb
### Quadratic time ###\ndef quadratic(n)\n  count = 0\n\n  # Number of iterations is quadratically related to the data size n\n  for i in 0...n\n    for j in 0...n\n      count += 1\n    end\n  end\n\n  count\nend\n

Figure 2-10 compares constant order, linear order, and quadratic order time complexities.

Figure 2-10   Time complexities of constant, linear, and quadratic orders

Taking bubble sort as an example, the outer loop executes \\(n - 1\\) times, and the inner loop executes \\(n-1\\), \\(n-2\\), \\(\\dots\\), \\(2\\), \\(1\\) times, averaging \\(n / 2\\) times, resulting in a time complexity of \\(O((n - 1) n / 2) = O(n^2)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def bubble_sort(nums: list[int]) -> int:\n    \"\"\"Quadratic order (bubble sort)\"\"\"\n    count = 0  # Counter\n    # Outer loop: unsorted range is [0, i]\n    for i in range(len(nums) - 1, 0, -1):\n        # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                tmp: int = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = tmp\n                count += 3  # Element swap includes 3 unit operations\n    return count\n
time_complexity.cpp
/* Quadratic order (bubble sort) */\nint bubbleSort(vector<int> &nums) {\n    int count = 0; // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.java
/* Quadratic order (bubble sort) */\nint bubbleSort(int[] nums) {\n    int count = 0; // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.cs
/* Quadratic order (bubble sort) */\nint BubbleSort(int[] nums) {\n    int count = 0;  // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n                count += 3;  // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.go
/* Quadratic order (bubble sort) */\nfunc bubbleSort(nums []int) int {\n    count := 0 // Counter\n    // Outer loop: unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                tmp := nums[j]\n                nums[j] = nums[j+1]\n                nums[j+1] = tmp\n                count += 3 // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count\n}\n
time_complexity.swift
/* Quadratic order (bubble sort) */\nfunc bubbleSort(nums: inout [Int]) -> Int {\n    var count = 0 // Counter\n    // Outer loop: unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = tmp\n                count += 3 // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count\n}\n
time_complexity.js
/* Quadratic order (bubble sort) */\nfunction bubbleSort(nums) {\n    let count = 0; // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.ts
/* Quadratic order (bubble sort) */\nfunction bubbleSort(nums: number[]): number {\n    let count = 0; // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.dart
/* Quadratic order (bubble sort) */\nint bubbleSort(List<int> nums) {\n  int count = 0; // Counter\n  // Outer loop: unsorted range is [0, i]\n  for (var i = nums.length - 1; i > 0; i--) {\n    // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for (var j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n        count += 3; // Element swap includes 3 unit operations\n      }\n    }\n  }\n  return count;\n}\n
time_complexity.rs
/* Quadratic order (bubble sort) */\nfn bubble_sort(nums: &mut [i32]) -> i32 {\n    let mut count = 0; // Counter\n\n    // Outer loop: unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    count\n}\n
time_complexity.c
/* Quadratic order (bubble sort) */\nint bubbleSort(int *nums, int n) {\n    int count = 0; // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (int i = n - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.kt
/* Quadratic order (bubble sort) */\nfun bubbleSort(nums: IntArray): Int {\n    var count = 0 // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n                count += 3 // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count\n}\n
time_complexity.rb
### Quadratic time (bubble sort) ###\ndef bubble_sort(nums)\n  count = 0  # Counter\n\n  # Outer loop: unsorted range is [0, i]\n  for i in (nums.length - 1).downto(0)\n    # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        tmp = nums[j]\n        nums[j] = nums[j + 1]\n        nums[j + 1] = tmp\n        count += 3 # Element swap includes 3 unit operations\n      end\n    end\n  end\n\n  count\nend\n
","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#4-exponential-order-o2n","level":3,"title":"4.   Exponential Order \\(O(2^n)\\)","text":"

Biological \"cell division\" is a typical example of exponential order growth: the initial state is \\(1\\) cell, after one round of division it becomes \\(2\\), after two rounds it becomes \\(4\\), and so on; after \\(n\\) rounds of division there are \\(2^n\\) cells.

Figure 2-11 and the following code simulate the cell division process, with a time complexity of \\(O(2^n)\\). Note that the input \\(n\\) represents the number of division rounds, and the return value count represents the total number of divisions.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def exponential(n: int) -> int:\n    \"\"\"Exponential order (loop implementation)\"\"\"\n    count = 0\n    base = 1\n    # Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in range(n):\n        for _ in range(base):\n            count += 1\n        base *= 2\n    # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n
time_complexity.cpp
/* Exponential order (loop implementation) */\nint exponential(int n) {\n    int count = 0, base = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.java
/* Exponential order (loop implementation) */\nint exponential(int n) {\n    int count = 0, base = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.cs
/* Exponential order (loop implementation) */\nint Exponential(int n) {\n    int count = 0, bas = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < bas; j++) {\n            count++;\n        }\n        bas *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.go
/* Exponential order (loop implementation) */\nfunc exponential(n int) int {\n    count, base := 0, 1\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for i := 0; i < n; i++ {\n        for j := 0; j < base; j++ {\n            count++\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
time_complexity.swift
/* Exponential order (loop implementation) */\nfunc exponential(n: Int) -> Int {\n    var count = 0\n    var base = 1\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in 0 ..< n {\n        for _ in 0 ..< base {\n            count += 1\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
time_complexity.js
/* Exponential order (loop implementation) */\nfunction exponential(n) {\n    let count = 0,\n        base = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.ts
/* Exponential order (loop implementation) */\nfunction exponential(n: number): number {\n    let count = 0,\n        base = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.dart
/* Exponential order (loop implementation) */\nint exponential(int n) {\n  int count = 0, base = 1;\n  // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n  for (var i = 0; i < n; i++) {\n    for (var j = 0; j < base; j++) {\n      count++;\n    }\n    base *= 2;\n  }\n  // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n  return count;\n}\n
time_complexity.rs
/* Exponential order (loop implementation) */\nfn exponential(n: i32) -> i32 {\n    let mut count = 0;\n    let mut base = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in 0..n {\n        for _ in 0..base {\n            count += 1\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    count\n}\n
time_complexity.c
/* Exponential order (loop implementation) */\nint exponential(int n) {\n    int count = 0;\n    int bas = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < bas; j++) {\n            count++;\n        }\n        bas *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.kt
/* Exponential order (loop implementation) */\nfun exponential(n: Int): Int {\n    var count = 0\n    var base = 1\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (i in 0..<n) {\n        for (j in 0..<base) {\n            count++\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
time_complexity.rb
### Exponential time (iterative) ###\ndef exponential(n)\n  count, base = 0, 1\n\n  # Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n  (0...n).each do\n    (0...base).each { count += 1 }\n    base *= 2\n  end\n\n  # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n  count\nend\n

Figure 2-11   Time complexity of exponential order

In actual algorithms, exponential order often appears in recursive functions. For example, in the following code, it recursively splits in two, stopping after \\(n\\) splits:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def exp_recur(n: int) -> int:\n    \"\"\"Exponential order (recursive implementation)\"\"\"\n    if n == 1:\n        return 1\n    return exp_recur(n - 1) + exp_recur(n - 1) + 1\n
time_complexity.cpp
/* Exponential order (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.java
/* Exponential order (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.cs
/* Exponential order (recursive implementation) */\nint ExpRecur(int n) {\n    if (n == 1) return 1;\n    return ExpRecur(n - 1) + ExpRecur(n - 1) + 1;\n}\n
time_complexity.go
/* Exponential order (recursive implementation) */\nfunc expRecur(n int) int {\n    if n == 1 {\n        return 1\n    }\n    return expRecur(n-1) + expRecur(n-1) + 1\n}\n
time_complexity.swift
/* Exponential order (recursive implementation) */\nfunc expRecur(n: Int) -> Int {\n    if n == 1 {\n        return 1\n    }\n    return expRecur(n: n - 1) + expRecur(n: n - 1) + 1\n}\n
time_complexity.js
/* Exponential order (recursive implementation) */\nfunction expRecur(n) {\n    if (n === 1) return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.ts
/* Exponential order (recursive implementation) */\nfunction expRecur(n: number): number {\n    if (n === 1) return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.dart
/* Exponential order (recursive implementation) */\nint expRecur(int n) {\n  if (n == 1) return 1;\n  return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.rs
/* Exponential order (recursive implementation) */\nfn exp_recur(n: i32) -> i32 {\n    if n == 1 {\n        return 1;\n    }\n    exp_recur(n - 1) + exp_recur(n - 1) + 1\n}\n
time_complexity.c
/* Exponential order (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.kt
/* Exponential order (recursive implementation) */\nfun expRecur(n: Int): Int {\n    if (n == 1) {\n        return 1\n    }\n    return expRecur(n - 1) + expRecur(n - 1) + 1\n}\n
time_complexity.rb
### Exponential time (recursive) ###\ndef exp_recur(n)\n  return 1 if n == 1\n  exp_recur(n - 1) + exp_recur(n - 1) + 1\nend\n

Exponential order growth is very rapid and is common in exhaustive methods (brute force search, backtracking, etc.). For problems with large data scales, exponential order is unacceptable and typically requires dynamic programming or greedy algorithms to solve.

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#5-logarithmic-order-olog-n","level":3,"title":"5.   Logarithmic Order \\(O(\\log n)\\)","text":"

In contrast to exponential order, logarithmic order reflects the situation of \"reducing to half each round\". Let the input data size be \\(n\\). Since it is reduced to half each round, the number of loops is \\(\\log_2 n\\), which is the inverse function of \\(2^n\\).

Figure 2-12 and the following code simulate the process of \"reducing to half each round\", with a time complexity of \\(O(\\log_2 n)\\), abbreviated as \\(O(\\log n)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def logarithmic(n: int) -> int:\n    \"\"\"Logarithmic order (loop implementation)\"\"\"\n    count = 0\n    while n > 1:\n        n = n / 2\n        count += 1\n    return count\n
time_complexity.cpp
/* Logarithmic order (loop implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.java
/* Logarithmic order (loop implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.cs
/* Logarithmic order (loop implementation) */\nint Logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n /= 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.go
/* Logarithmic order (loop implementation) */\nfunc logarithmic(n int) int {\n    count := 0\n    for n > 1 {\n        n = n / 2\n        count++\n    }\n    return count\n}\n
time_complexity.swift
/* Logarithmic order (loop implementation) */\nfunc logarithmic(n: Int) -> Int {\n    var count = 0\n    var n = n\n    while n > 1 {\n        n = n / 2\n        count += 1\n    }\n    return count\n}\n
time_complexity.js
/* Logarithmic order (loop implementation) */\nfunction logarithmic(n) {\n    let count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.ts
/* Logarithmic order (loop implementation) */\nfunction logarithmic(n: number): number {\n    let count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.dart
/* Logarithmic order (loop implementation) */\nint logarithmic(int n) {\n  int count = 0;\n  while (n > 1) {\n    n = n ~/ 2;\n    count++;\n  }\n  return count;\n}\n
time_complexity.rs
/* Logarithmic order (loop implementation) */\nfn logarithmic(mut n: i32) -> i32 {\n    let mut count = 0;\n    while n > 1 {\n        n = n / 2;\n        count += 1;\n    }\n    count\n}\n
time_complexity.c
/* Logarithmic order (loop implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.kt
/* Logarithmic order (loop implementation) */\nfun logarithmic(n: Int): Int {\n    var n1 = n\n    var count = 0\n    while (n1 > 1) {\n        n1 /= 2\n        count++\n    }\n    return count\n}\n
time_complexity.rb
### Logarithmic time (iterative) ###\ndef logarithmic(n)\n  count = 0\n\n  while n > 1\n    n /= 2\n    count += 1\n  end\n\n  count\nend\n

Figure 2-12   Time complexity of logarithmic order

Like exponential order, logarithmic order also commonly appears in recursive functions. The following code forms a recursion tree of height \\(\\log_2 n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def log_recur(n: int) -> int:\n    \"\"\"Logarithmic order (recursive implementation)\"\"\"\n    if n <= 1:\n        return 0\n    return log_recur(n / 2) + 1\n
time_complexity.cpp
/* Logarithmic order (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
time_complexity.java
/* Logarithmic order (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
time_complexity.cs
/* Logarithmic order (recursive implementation) */\nint LogRecur(int n) {\n    if (n <= 1) return 0;\n    return LogRecur(n / 2) + 1;\n}\n
time_complexity.go
/* Logarithmic order (recursive implementation) */\nfunc logRecur(n int) int {\n    if n <= 1 {\n        return 0\n    }\n    return logRecur(n/2) + 1\n}\n
time_complexity.swift
/* Logarithmic order (recursive implementation) */\nfunc logRecur(n: Int) -> Int {\n    if n <= 1 {\n        return 0\n    }\n    return logRecur(n: n / 2) + 1\n}\n
time_complexity.js
/* Logarithmic order (recursive implementation) */\nfunction logRecur(n) {\n    if (n <= 1) return 0;\n    return logRecur(n / 2) + 1;\n}\n
time_complexity.ts
/* Logarithmic order (recursive implementation) */\nfunction logRecur(n: number): number {\n    if (n <= 1) return 0;\n    return logRecur(n / 2) + 1;\n}\n
time_complexity.dart
/* Logarithmic order (recursive implementation) */\nint logRecur(int n) {\n  if (n <= 1) return 0;\n  return logRecur(n ~/ 2) + 1;\n}\n
time_complexity.rs
/* Logarithmic order (recursive implementation) */\nfn log_recur(n: i32) -> i32 {\n    if n <= 1 {\n        return 0;\n    }\n    log_recur(n / 2) + 1\n}\n
time_complexity.c
/* Logarithmic order (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
time_complexity.kt
/* Logarithmic order (recursive implementation) */\nfun logRecur(n: Int): Int {\n    if (n <= 1)\n        return 0\n    return logRecur(n / 2) + 1\n}\n
time_complexity.rb
### Logarithmic time (recursive) ###\ndef log_recur(n)\n  return 0 unless n > 1\n  log_recur(n / 2) + 1\nend\n

Logarithmic order commonly appears in algorithms based on the divide-and-conquer strategy, embodying the algorithmic thinking of \"dividing into many\" and \"simplifying complexity\". It grows slowly and is the ideal time complexity second only to constant order.

What is the base of \\(O(\\log n)\\)?

To be precise, \"dividing into \\(m\\)\" corresponds to a time complexity of \\(O(\\log_m n)\\). And through the logarithmic base change formula, we can obtain time complexities with different bases that are equal:

\\[ O(\\log_m n) = O(\\log_k n / \\log_k m) = O(\\log_k n) \\]

That is to say, the base \\(m\\) can be converted without affecting the complexity. Therefore, we usually omit the base \\(m\\) and denote logarithmic order simply as \\(O(\\log n)\\).

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#6-linearithmic-order-on-log-n","level":3,"title":"6.   Linearithmic Order \\(O(n \\log n)\\)","text":"

Linearithmic order commonly appears in nested loops, where the time complexities of the two layers of loops are \\(O(\\log n)\\) and \\(O(n)\\) respectively. The relevant code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def linear_log_recur(n: int) -> int:\n    \"\"\"Linearithmic order\"\"\"\n    if n <= 1:\n        return 1\n    # Divide into two, the scale of subproblems is reduced by half\n    count = linear_log_recur(n // 2) + linear_log_recur(n // 2)\n    # Current subproblem contains n operations\n    for _ in range(n):\n        count += 1\n    return count\n
time_complexity.cpp
/* Linearithmic order */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.java
/* Linearithmic order */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.cs
/* Linearithmic order */\nint LinearLogRecur(int n) {\n    if (n <= 1) return 1;\n    int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.go
/* Linearithmic order */\nfunc linearLogRecur(n int) int {\n    if n <= 1 {\n        return 1\n    }\n    count := linearLogRecur(n/2) + linearLogRecur(n/2)\n    for i := 0; i < n; i++ {\n        count++\n    }\n    return count\n}\n
time_complexity.swift
/* Linearithmic order */\nfunc linearLogRecur(n: Int) -> Int {\n    if n <= 1 {\n        return 1\n    }\n    var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2)\n    for _ in stride(from: 0, to: n, by: 1) {\n        count += 1\n    }\n    return count\n}\n
time_complexity.js
/* Linearithmic order */\nfunction linearLogRecur(n) {\n    if (n <= 1) return 1;\n    let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (let i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.ts
/* Linearithmic order */\nfunction linearLogRecur(n: number): number {\n    if (n <= 1) return 1;\n    let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (let i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.dart
/* Linearithmic order */\nint linearLogRecur(int n) {\n  if (n <= 1) return 1;\n  int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2);\n  for (var i = 0; i < n; i++) {\n    count++;\n  }\n  return count;\n}\n
time_complexity.rs
/* Linearithmic order */\nfn linear_log_recur(n: i32) -> i32 {\n    if n <= 1 {\n        return 1;\n    }\n    let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2);\n    for _ in 0..n {\n        count += 1;\n    }\n    return count;\n}\n
time_complexity.c
/* Linearithmic order */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.kt
/* Linearithmic order */\nfun linearLogRecur(n: Int): Int {\n    if (n <= 1)\n        return 1\n    var count = linearLogRecur(n / 2) + linearLogRecur(n / 2)\n    for (i in 0..<n) {\n        count++\n    }\n    return count\n}\n
time_complexity.rb
### Linearithmic time ###\ndef linear_log_recur(n)\n  return 1 unless n > 1\n\n  count = linear_log_recur(n / 2) + linear_log_recur(n / 2)\n  (0...n).each { count += 1 }\n\n  count\nend\n

Figure 2-13 shows how linearithmic order is generated. Each level of the binary tree has a total of \\(n\\) operations, and the tree has \\(\\log_2 n + 1\\) levels, resulting in a time complexity of \\(O(n \\log n)\\).

Figure 2-13   Time complexity of linearithmic order

Mainstream sorting algorithms typically have a time complexity of \\(O(n \\log n)\\), such as quicksort, merge sort, and heap sort.

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#7-factorial-order-on","level":3,"title":"7.   Factorial Order \\(O(n!)\\)","text":"

Factorial order corresponds to the mathematical \"permutation\" problem. Given \\(n\\) distinct elements, find all possible permutation schemes; the number of schemes is:

\\[ n! = n \\times (n - 1) \\times (n - 2) \\times \\dots \\times 2 \\times 1 \\]

Factorials are typically implemented using recursion. As shown in Figure 2-14 and the following code, the first level splits into \\(n\\) branches, the second level splits into \\(n - 1\\) branches, and so on, until the \\(n\\)-th level when splitting stops:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def factorial_recur(n: int) -> int:\n    \"\"\"Factorial order (recursive implementation)\"\"\"\n    if n == 0:\n        return 1\n    count = 0\n    # Split from 1 into n\n    for _ in range(n):\n        count += factorial_recur(n - 1)\n    return count\n
time_complexity.cpp
/* Factorial order (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    // Split from 1 into n\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.java
/* Factorial order (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    // Split from 1 into n\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.cs
/* Factorial order (recursive implementation) */\nint FactorialRecur(int n) {\n    if (n == 0) return 1;\n    int count = 0;\n    // Split from 1 into n\n    for (int i = 0; i < n; i++) {\n        count += FactorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.go
/* Factorial order (recursive implementation) */\nfunc factorialRecur(n int) int {\n    if n == 0 {\n        return 1\n    }\n    count := 0\n    // Split from 1 into n\n    for i := 0; i < n; i++ {\n        count += factorialRecur(n - 1)\n    }\n    return count\n}\n
time_complexity.swift
/* Factorial order (recursive implementation) */\nfunc factorialRecur(n: Int) -> Int {\n    if n == 0 {\n        return 1\n    }\n    var count = 0\n    // Split from 1 into n\n    for _ in 0 ..< n {\n        count += factorialRecur(n: n - 1)\n    }\n    return count\n}\n
time_complexity.js
/* Factorial order (recursive implementation) */\nfunction factorialRecur(n) {\n    if (n === 0) return 1;\n    let count = 0;\n    // Split from 1 into n\n    for (let i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.ts
/* Factorial order (recursive implementation) */\nfunction factorialRecur(n: number): number {\n    if (n === 0) return 1;\n    let count = 0;\n    // Split from 1 into n\n    for (let i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.dart
/* Factorial order (recursive implementation) */\nint factorialRecur(int n) {\n  if (n == 0) return 1;\n  int count = 0;\n  // Split from 1 into n\n  for (var i = 0; i < n; i++) {\n    count += factorialRecur(n - 1);\n  }\n  return count;\n}\n
time_complexity.rs
/* Factorial order (recursive implementation) */\nfn factorial_recur(n: i32) -> i32 {\n    if n == 0 {\n        return 1;\n    }\n    let mut count = 0;\n    // Split from 1 into n\n    for _ in 0..n {\n        count += factorial_recur(n - 1);\n    }\n    count\n}\n
time_complexity.c
/* Factorial order (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.kt
/* Factorial order (recursive implementation) */\nfun factorialRecur(n: Int): Int {\n    if (n == 0)\n        return 1\n    var count = 0\n    // Split from 1 into n\n    for (i in 0..<n) {\n        count += factorialRecur(n - 1)\n    }\n    return count\n}\n
time_complexity.rb
### Factorial time (recursive) ###\ndef factorial_recur(n)\n  return 1 if n == 0\n\n  count = 0\n  # Split from 1 into n\n  (0...n).each { count += factorial_recur(n - 1) }\n\n  count\nend\n

Figure 2-14   Time complexity of factorial order

Note that because when \\(n \\geq 4\\) we always have \\(n! > 2^n\\), factorial order grows faster than exponential order, and is also unacceptable for large \\(n\\).

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#235-worst-best-and-average-time-complexities","level":2,"title":"2.3.5   Worst, Best, and Average Time Complexities","text":"

The time efficiency of an algorithm is often not fixed, but is related to the distribution of the input data. Suppose we input an array nums of length \\(n\\), where nums consists of numbers from \\(1\\) to \\(n\\), with each number appearing only once, but the element order is randomly shuffled. The task is to return the index of element \\(1\\). We can draw the following conclusions.

  • When nums = [?, ?, ..., 1], i.e., when the last element is \\(1\\), it requires a complete traversal of the array, reaching worst-case time complexity \\(O(n)\\).
  • When nums = [1, ?, ?, ...], i.e., when the first element is \\(1\\), no matter how long the array is, there is no need to continue traversing, reaching best-case time complexity \\(\\Omega(1)\\).

The \"worst-case time complexity\" corresponds to the function's asymptotic upper bound, denoted using big-\\(O\\) notation. Correspondingly, the \"best-case time complexity\" corresponds to the function's asymptotic lower bound, denoted using \\(\\Omega\\) notation:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby worst_best_time_complexity.py
def random_numbers(n: int) -> list[int]:\n    \"\"\"Generate an array with elements: 1, 2, ..., n, shuffled in order\"\"\"\n    # Generate array nums =: 1, 2, 3, ..., n\n    nums = [i for i in range(1, n + 1)]\n    # Randomly shuffle array elements\n    random.shuffle(nums)\n    return nums\n\ndef find_one(nums: list[int]) -> int:\n    \"\"\"Find the index of number 1 in array nums\"\"\"\n    for i in range(len(nums)):\n        # When element 1 is at the head of the array, best time complexity O(1) is achieved\n        # When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if nums[i] == 1:\n            return i\n    return -1\n
worst_best_time_complexity.cpp
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nvector<int> randomNumbers(int n) {\n    vector<int> nums(n);\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Use system time to generate random seed\n    unsigned seed = chrono::system_clock::now().time_since_epoch().count();\n    // Randomly shuffle array elements\n    shuffle(nums.begin(), nums.end(), default_random_engine(seed));\n    return nums;\n}\n\n/* Find the index of number 1 in array nums */\nint findOne(vector<int> &nums) {\n    for (int i = 0; i < nums.size(); i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
worst_best_time_complexity.java
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nint[] randomNumbers(int n) {\n    Integer[] nums = new Integer[n];\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle array elements\n    Collections.shuffle(Arrays.asList(nums));\n    // Integer[] -> int[]\n    int[] res = new int[n];\n    for (int i = 0; i < n; i++) {\n        res[i] = nums[i];\n    }\n    return res;\n}\n\n/* Find the index of number 1 in array nums */\nint findOne(int[] nums) {\n    for (int i = 0; i < nums.length; i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
worst_best_time_complexity.cs
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nint[] RandomNumbers(int n) {\n    int[] nums = new int[n];\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n\n    // Randomly shuffle array elements\n    for (int i = 0; i < nums.Length; i++) {\n        int index = new Random().Next(i, nums.Length);\n        (nums[i], nums[index]) = (nums[index], nums[i]);\n    }\n    return nums;\n}\n\n/* Find the index of number 1 in array nums */\nint FindOne(int[] nums) {\n    for (int i = 0; i < nums.Length; i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
worst_best_time_complexity.go
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfunc randomNumbers(n int) []int {\n    nums := make([]int, n)\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for i := 0; i < n; i++ {\n        nums[i] = i + 1\n    }\n    // Randomly shuffle array elements\n    rand.Shuffle(len(nums), func(i, j int) {\n        nums[i], nums[j] = nums[j], nums[i]\n    })\n    return nums\n}\n\n/* Find the index of number 1 in array nums */\nfunc findOne(nums []int) int {\n    for i := 0; i < len(nums); i++ {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if nums[i] == 1 {\n            return i\n        }\n    }\n    return -1\n}\n
worst_best_time_complexity.swift
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfunc randomNumbers(n: Int) -> [Int] {\n    // Generate array nums = { 1, 2, 3, ..., n }\n    var nums = Array(1 ... n)\n    // Randomly shuffle array elements\n    nums.shuffle()\n    return nums\n}\n\n/* Find the index of number 1 in array nums */\nfunc findOne(nums: [Int]) -> Int {\n    for i in nums.indices {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if nums[i] == 1 {\n            return i\n        }\n    }\n    return -1\n}\n
worst_best_time_complexity.js
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfunction randomNumbers(n) {\n    const nums = Array(n);\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (let i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle array elements\n    for (let i = 0; i < n; i++) {\n        const r = Math.floor(Math.random() * (i + 1));\n        const temp = nums[i];\n        nums[i] = nums[r];\n        nums[r] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of number 1 in array nums */\nfunction findOne(nums) {\n    for (let i = 0; i < nums.length; i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] === 1) {\n            return i;\n        }\n    }\n    return -1;\n}\n
worst_best_time_complexity.ts
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfunction randomNumbers(n: number): number[] {\n    const nums = Array(n);\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (let i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle array elements\n    for (let i = 0; i < n; i++) {\n        const r = Math.floor(Math.random() * (i + 1));\n        const temp = nums[i];\n        nums[i] = nums[r];\n        nums[r] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of number 1 in array nums */\nfunction findOne(nums: number[]): number {\n    for (let i = 0; i < nums.length; i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] === 1) {\n            return i;\n        }\n    }\n    return -1;\n}\n
worst_best_time_complexity.dart
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nList<int> randomNumbers(int n) {\n  final nums = List.filled(n, 0);\n  // Generate array nums = { 1, 2, 3, ..., n }\n  for (var i = 0; i < n; i++) {\n    nums[i] = i + 1;\n  }\n  // Randomly shuffle array elements\n  nums.shuffle();\n\n  return nums;\n}\n\n/* Find the index of number 1 in array nums */\nint findOne(List<int> nums) {\n  for (var i = 0; i < nums.length; i++) {\n    // When element 1 is at the head of the array, best time complexity O(1) is achieved\n    // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n    if (nums[i] == 1) return i;\n  }\n\n  return -1;\n}\n
worst_best_time_complexity.rs
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfn random_numbers(n: i32) -> Vec<i32> {\n    // Generate array nums = { 1, 2, 3, ..., n }\n    let mut nums = (1..=n).collect::<Vec<i32>>();\n    // Randomly shuffle array elements\n    nums.shuffle(&mut thread_rng());\n    nums\n}\n\n/* Find the index of number 1 in array nums */\nfn find_one(nums: &[i32]) -> Option<usize> {\n    for i in 0..nums.len() {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if nums[i] == 1 {\n            return Some(i);\n        }\n    }\n    None\n}\n
worst_best_time_complexity.c
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nint *randomNumbers(int n) {\n    // Allocate heap memory (create 1D variable-length array: n elements of type int)\n    int *nums = (int *)malloc(n * sizeof(int));\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle array elements\n    for (int i = n - 1; i > 0; i--) {\n        int j = rand() % (i + 1);\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of number 1 in array nums */\nint findOne(int *nums, int n) {\n    for (int i = 0; i < n; i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
worst_best_time_complexity.kt
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfun randomNumbers(n: Int): Array<Int?> {\n    val nums = IntArray(n)\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (i in 0..<n) {\n        nums[i] = i + 1\n    }\n    // Randomly shuffle array elements\n    nums.shuffle()\n    val res = arrayOfNulls<Int>(n)\n    for (i in 0..<n) {\n        res[i] = nums[i]\n    }\n    return res\n}\n\n/* Find the index of number 1 in array nums */\nfun findOne(nums: Array<Int?>): Int {\n    for (i in nums.indices) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] == 1)\n            return i\n    }\n    return -1\n}\n
worst_best_time_complexity.rb
### Generate array with elements: 1, 2, ..., n, shuffled ###\ndef random_numbers(n)\n  # Generate array nums =: 1, 2, 3, ..., n\n  nums = Array.new(n) { |i| i + 1 }\n  # Randomly shuffle array elements\n  nums.shuffle!\nend\n\n### Find index of number 1 in array nums ###\ndef find_one(nums)\n  for i in 0...nums.length\n    # When element 1 is at the head of the array, best time complexity O(1) is achieved\n    # When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n    return i if nums[i] == 1\n  end\n\n  -1\nend\n

It is worth noting that we rarely use best-case time complexity in practice, because it can usually only be achieved with a very small probability and may be somewhat misleading. The worst-case time complexity is more practical because it gives a safety value for efficiency, allowing us to use the algorithm with confidence.

From the above example, we can see that both worst-case and best-case time complexities only occur under \"special data distributions\", which may have a very small probability of occurrence and may not truly reflect the algorithm's running efficiency. In contrast, average time complexity can reflect the algorithm's running efficiency under random input data, denoted using the \\(\\Theta\\) notation.

For some algorithms, we can simply derive the average case under random data distribution. For example, in the above example, since the input array is shuffled, the probability of element \\(1\\) appearing at any index is equal, so the algorithm's average number of loops is half the array length \\(n / 2\\), giving an average time complexity of \\(\\Theta(n / 2) = \\Theta(n)\\).

But for more complex algorithms, calculating average time complexity is often quite difficult, because it is hard to analyze the overall mathematical expectation under data distribution. In this case, we usually use worst-case time complexity as the criterion for judging algorithm efficiency.

Why is the \\(\\Theta\\) symbol rarely seen?

This may be because the \\(O\\) symbol is too catchy, so we often use it to represent average time complexity. But strictly speaking, this practice is not standard. In this book and other materials, if you encounter expressions like \"average time complexity \\(O(n)\\)\", please understand it directly as \\(\\Theta(n)\\).

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_data_structure/","level":1,"title":"Chapter 3.   Data Structures","text":"

Abstract

Data structure is like a sturdy and diverse framework.

It provides a blueprint for the orderly organization of data, upon which algorithms come to life.

","path":["Chapter 3. Data Structures","Chapter 3.   Data Structures"],"tags":[]},{"location":"chapter_data_structure/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 3.1   Classification of Data Structures
  • 3.2   Basic Data Types
  • 3.3   Number Encoding *
  • 3.4   Character Encoding *
  • 3.5   Summary
","path":["Chapter 3. Data Structures","Chapter 3.   Data Structures"],"tags":[]},{"location":"chapter_data_structure/basic_data_types/","level":1,"title":"3.2   Basic Data Types","text":"

When we talk about data in computers, we think of various forms such as text, images, videos, audio, 3D models, and more. Although these data are organized in different ways, they are all composed of various basic data types.

Basic data types are types that the CPU can directly operate on, and they are directly used in algorithms, mainly including the following.

  • Integer types byte, short, int, long.
  • Floating-point types float, double, used to represent decimal numbers.
  • Character type char, used to represent letters, punctuation marks, and even emojis in various languages.
  • Boolean type bool, used to represent \"yes\" and \"no\" judgments.

Basic data types are stored in binary form in computers. One binary bit is \\(1\\) bit. In most modern operating systems, \\(1\\) byte consists of \\(8\\) bits.

The range of values for basic data types depends on the size of the space they occupy. Below is an example using Java.

  • Integer type byte occupies \\(1\\) byte = \\(8\\) bits, and can represent \\(2^{8}\\) numbers.
  • Integer type int occupies \\(4\\) bytes = \\(32\\) bits, and can represent \\(2^{32}\\) numbers.

The following table lists the space occupied, value ranges, and default values of various basic data types in Java. You don't need to memorize this table; a general understanding is sufficient, and you can refer to it when needed.

Table 3-1   Space occupied and value ranges of basic data types

Type Symbol Space Occupied Minimum Value Maximum Value Default Value Integer byte 1 byte \\(-2^7\\) (\\(-128\\)) \\(2^7 - 1\\) (\\(127\\)) \\(0\\) short 2 bytes \\(-2^{15}\\) \\(2^{15} - 1\\) \\(0\\) int 4 bytes \\(-2^{31}\\) \\(2^{31} - 1\\) \\(0\\) long 8 bytes \\(-2^{63}\\) \\(2^{63} - 1\\) \\(0\\) Float float 4 bytes \\(1.175 \\times 10^{-38}\\) \\(3.403 \\times 10^{38}\\) \\(0.0\\text{f}\\) double 8 bytes \\(2.225 \\times 10^{-308}\\) \\(1.798 \\times 10^{308}\\) \\(0.0\\) Character char 2 bytes \\(0\\) \\(2^{16} - 1\\) \\(0\\) Boolean bool 1 byte \\(\\text{false}\\) \\(\\text{true}\\) \\(\\text{false}\\)

Please note that the above table is specific to Java's basic data types. Each programming language has its own data type definitions, and their space occupied, value ranges, and default values may vary.

  • In Python, the integer type int can be of any size, limited only by available memory; the floating-point type float is double-precision 64-bit; there is no char type, a single character is actually a string str of length 1.
  • C and C++ do not explicitly specify the size of basic data types, which varies by implementation and platform. The above table follows the LP64 data model, which is used in Unix 64-bit operating systems including Linux and macOS.
  • The size of character char is 1 byte in C and C++, and in most programming languages it depends on the specific character encoding method, as detailed in the \"Character Encoding\" section.
  • Even though representing a boolean value requires only 1 bit (\\(0\\) or \\(1\\)), it is usually stored as 1 byte in memory. This is because modern computer CPUs typically use 1 byte as the minimum addressable memory unit.

So, what is the relationship between basic data types and data structures? We know that data structures are ways of organizing and storing data in computers. The subject of this statement is \"structure\", not \"data\".

If we want to represent \"a row of numbers\", we naturally think of using an array. This is because the linear structure of an array can represent the adjacency and order relationships of numbers, but the content stored—whether integer int, floating-point float, or character char—is unrelated to the \"data structure\".

In other words, basic data types provide the \"content type\" of data, while data structures provide the \"organization method\" of data. For example, in the following code, we use the same data structure (array) to store and represent different basic data types, including int, float, char, bool, etc.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
# Initialize arrays using various basic data types\nnumbers: list[int] = [0] * 5\ndecimals: list[float] = [0.0] * 5\n# In Python, characters are actually strings of length 1\ncharacters: list[str] = ['0'] * 5\nbools: list[bool] = [False] * 5\n# Python lists can freely store various basic data types and object references\ndata = [0, 0.0, 'a', False, ListNode(0)]\n
// Initialize arrays using various basic data types\nint numbers[5];\nfloat decimals[5];\nchar characters[5];\nbool bools[5];\n
// Initialize arrays using various basic data types\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nboolean[] bools = new boolean[5];\n
// Initialize arrays using various basic data types\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nbool[] bools = new bool[5];\n
// Initialize arrays using various basic data types\nvar numbers = [5]int{}\nvar decimals = [5]float64{}\nvar characters = [5]byte{}\nvar bools = [5]bool{}\n
// Initialize arrays using various basic data types\nlet numbers = Array(repeating: 0, count: 5)\nlet decimals = Array(repeating: 0.0, count: 5)\nlet characters: [Character] = Array(repeating: \"a\", count: 5)\nlet bools = Array(repeating: false, count: 5)\n
// JavaScript arrays can freely store various basic data types and objects\nconst array = [0, 0.0, 'a', false];\n
// Initialize arrays using various basic data types\nconst numbers: number[] = [];\nconst characters: string[] = [];\nconst bools: boolean[] = [];\n
// Initialize arrays using various basic data types\nList<int> numbers = List.filled(5, 0);\nList<double> decimals = List.filled(5, 0.0);\nList<String> characters = List.filled(5, 'a');\nList<bool> bools = List.filled(5, false);\n
// Initialize arrays using various basic data types\nlet numbers: Vec<i32> = vec![0; 5];\nlet decimals: Vec<f32> = vec![0.0; 5];\nlet characters: Vec<char> = vec!['0'; 5];\nlet bools: Vec<bool> = vec![false; 5];\n
// Initialize arrays using various basic data types\nint numbers[10];\nfloat decimals[10];\nchar characters[10];\nbool bools[10];\n
// Initialize arrays using various basic data types\nval numbers = IntArray(5)\nval decinals = FloatArray(5)\nval characters = CharArray(5)\nval bools = BooleanArray(5)\n
# Ruby lists can freely store various basic data types and object references\ndata = [0, 0.0, 'a', false, ListNode(0)]\n
Visualized Execution

https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%9D%A5%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E7%AC%A6%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%AD%98%E5%82%A8%E5%90%84%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

","path":["Chapter 3. Data Structures","3.2   Basic Data Types"],"tags":[]},{"location":"chapter_data_structure/character_encoding/","level":1,"title":"3.4   Character Encoding *","text":"

In computers, all data is stored in binary form, and character char is no exception. To represent characters, we need to establish a \"character set\" that defines a one-to-one correspondence between each character and binary numbers. With a character set, computers can convert binary numbers to characters by looking up the table.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#341-ascii-character-set","level":2,"title":"3.4.1   Ascii Character Set","text":"

ASCII code is the earliest character set, with the full name American Standard Code for Information Interchange. It uses 7 binary bits (the lower 7 bits of one byte) to represent a character, and can represent a maximum of 128 different characters. As shown in Figure 3-6, ASCII code includes uppercase and lowercase English letters, numbers 0 ~ 9, some punctuation marks, and some control characters (such as newline and tab).

Figure 3-6   ASCII code

However, ASCII code can only represent English. With the globalization of computers, a character set called EASCII that can represent more languages emerged. It expands from the 7-bit basis of ASCII to 8 bits, and can represent 256 different characters.

Worldwide, a batch of EASCII character sets suitable for different regions have appeared successively. The first 128 characters of these character sets are unified as ASCII code, and the last 128 characters are defined differently to adapt to the needs of different languages.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#342-gbk-character-set","level":2,"title":"3.4.2   Gbk Character Set","text":"

Later, people found that EASCII code still cannot meet the character quantity requirements of many languages. For example, there are nearly one hundred thousand Chinese characters, and several thousand are used daily. In 1980, the China National Standardization Administration released the GB2312 character set, which included 6,763 Chinese characters, basically meeting the needs for computer processing of Chinese characters.

However, GB2312 cannot handle some rare characters and traditional Chinese characters. The GBK character set is an extension based on GB2312, which includes a total of 21,886 Chinese characters. In the GBK encoding scheme, ASCII characters are represented using one byte, and Chinese characters are represented using two bytes.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#343-unicode-character-set","level":2,"title":"3.4.3   Unicode Character Set","text":"

With the vigorous development of computer technology, character sets and encoding standards flourished, which brought many problems. On the one hand, these character sets generally only define characters for specific languages and cannot work normally in multilingual environments. On the other hand, multiple character set standards exist for the same language, and if two computers use different encoding standards, garbled characters will appear during information transmission.

Researchers of that era thought: If a sufficiently complete character set is released that includes all languages and symbols in the world, wouldn't it be possible to solve cross-language environment and garbled character problems? Driven by this idea, a large and comprehensive character set, Unicode, was born.

Unicode is called \"统一码\" (Unified Code) in Chinese and can theoretically accommodate over one million characters. It is committed to including characters from around the world into a unified character set, providing a universal character set to handle and display various language texts, reducing garbled character problems caused by different encoding standards.

Since its release in 1991, Unicode has continuously expanded to include new languages and characters. As of September 2022, Unicode has included 149,186 characters, including characters, symbols, and even emojis from various languages. In the vast Unicode character set, commonly used characters occupy 2 bytes, and some rare characters occupy 3 bytes or even 4 bytes.

Unicode is a universal character set that essentially assigns a number (called a \"code point\") to each character, but it does not specify how to store these character code points in computers. We can't help but ask: when Unicode code points of multiple lengths appear simultaneously in a text, how does the system parse the characters? For example, given an encoding with a length of 2 bytes, how does the system determine whether it is one 2-byte character or two 1-byte characters?

For the above problem, a straightforward solution is to store all characters as equal-length encodings. As shown in Figure 3-7, each character in \"Hello\" occupies 1 byte, and each character in \"算法\" (algorithm) occupies 2 bytes. We can encode all characters in \"Hello 算法\" as 2 bytes in length by padding the high bits with 0. In this way, the system can parse one character every 2 bytes and restore the content of this phrase.

Figure 3-7   Unicode encoding example

However, ASCII code has already proven to us that encoding English only requires 1 byte. If the above scheme is adopted, the size of English text will be twice that under ASCII encoding, which is very wasteful of memory space. Therefore, we need a more efficient Unicode encoding method.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#344-utf-8-encoding","level":2,"title":"3.4.4   Utf-8 Encoding","text":"

Currently, UTF-8 has become the most widely used Unicode encoding method internationally. It is a variable-length encoding that uses 1 to 4 bytes to represent a character, depending on the complexity of the character. ASCII characters only require 1 byte, Latin and Greek letters require 2 bytes, commonly used Chinese characters require 3 bytes, and some other rare characters require 4 bytes.

The encoding rules of UTF-8 are not complicated and can be divided into the following two cases.

  • For 1-byte characters, set the highest bit to \\(0\\), and set the remaining 7 bits to the Unicode code point. It is worth noting that ASCII characters occupy the first 128 code points in the Unicode character set. That is to say, UTF-8 encoding is backward compatible with ASCII code. This means we can use UTF-8 to parse very old ASCII code text.
  • For characters with a length of \\(n\\) bytes (where \\(n > 1\\)), set the highest \\(n\\) bits of the first byte to \\(1\\), and set the \\((n + 1)\\)-th bit to \\(0\\); starting from the second byte, set the highest 2 bits of each byte to \\(10\\); use all remaining bits to fill in the Unicode code point of the character.

Figure 3-8 shows the UTF-8 encoding corresponding to \"Hello算法\". It can be observed that since the highest \\(n\\) bits are all set to \\(1\\), the system can parse the length of the character as \\(n\\) by reading the number of highest bits that are \\(1\\).

But why set the highest 2 bits of all other bytes to \\(10\\)? In fact, this \\(10\\) can serve as a check symbol. Assuming the system starts parsing text from an incorrect byte, the \\(10\\) at the beginning of the byte can help the system quickly determine an anomaly.

The reason for using \\(10\\) as a check symbol is that under UTF-8 encoding rules, it is impossible for a character's highest two bits to be \\(10\\). This conclusion can be proven by contradiction: assuming the highest two bits of a character are \\(10\\), it means the length of the character is \\(1\\), corresponding to ASCII code. However, the highest bit of ASCII code should be \\(0\\), which contradicts the assumption.

Figure 3-8   UTF-8 encoding example

In addition to UTF-8, common encoding methods also include the following two.

  • UTF-16 encoding: Uses 2 or 4 bytes to represent a character. All ASCII characters and commonly used non-English characters are represented with 2 bytes; a few characters need to use 4 bytes. For 2-byte characters, UTF-16 encoding is equal to the Unicode code point.
  • UTF-32 encoding: Every character uses 4 bytes. This means that UTF-32 takes up more space than UTF-8 and UTF-16, especially for text with a high proportion of ASCII characters.

From the perspective of storage space occupation, using UTF-8 to represent English characters is very efficient because it only requires 1 byte; using UTF-16 encoding for some non-English characters (such as Chinese) will be more efficient because it only requires 2 bytes, while UTF-8 may require 3 bytes.

From a compatibility perspective, UTF-8 has the best universality, and many tools and libraries support UTF-8 first.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#345-character-encoding-in-programming-languages","level":2,"title":"3.4.5   Character Encoding in Programming Languages","text":"

For most past programming languages, strings during program execution use fixed-length encodings such as UTF-16 or UTF-32. Under fixed-length encoding, we can treat strings as arrays for processing, and this approach has the following advantages.

  • Random access: UTF-16 encoded strings can be easily accessed randomly. UTF-8 is a variable-length encoding. To find the \\(i\\)-th character, we need to traverse from the beginning of the string to the \\(i\\)-th character, which requires \\(O(n)\\) time.
  • Character counting: Similar to random access, calculating the length of a UTF-16 encoded string is also an \\(O(1)\\) operation. However, calculating the length of a UTF-8 encoded string requires traversing the entire string.
  • String operations: Many string operations (such as splitting, joining, inserting, deleting, etc.) on UTF-16 encoded strings are easier to perform. Performing these operations on UTF-8 encoded strings usually requires additional calculations to ensure that invalid UTF-8 encoding is not generated.

In fact, the design of character encoding schemes for programming languages is a very interesting topic involving many factors.

  • Java's String type uses UTF-16 encoding, with each character occupying 2 bytes. This is because at the beginning of Java language design, people believed that 16 bits were sufficient to represent all possible characters. However, this was an incorrect judgment. Later, the Unicode specification expanded beyond 16 bits, so characters in Java may now be represented by a pair of 16-bit values (called \"surrogate pairs\").
  • The strings of JavaScript and TypeScript use UTF-16 encoding for reasons similar to Java. When Netscape first introduced the JavaScript language in 1995, Unicode was still in its early stages of development, and at that time, using 16-bit encoding was sufficient to represent all Unicode characters.
  • C# uses UTF-16 encoding mainly because the .NET platform was designed by Microsoft, and many of Microsoft's technologies (including the Windows operating system) extensively use UTF-16 encoding.

Due to the underestimation of character quantities by the above programming languages, they had to adopt the \"surrogate pair\" method to represent Unicode characters with lengths exceeding 16 bits. This is a reluctant compromise. On the one hand, in strings containing surrogate pairs, one character may occupy 2 bytes or 4 bytes, thus losing the advantage of fixed-length encoding. On the other hand, handling surrogate pairs requires additional code, which increases the complexity and difficulty of debugging in programming.

For the above reasons, some programming languages have proposed different encoding schemes.

  • Python's str uses Unicode encoding and adopts a flexible string representation where the stored character length depends on the largest Unicode code point in the string. If all characters in the string are ASCII characters, each character occupies 1 byte; if there are characters exceeding the ASCII range but all within the Basic Multilingual Plane (BMP), each character occupies 2 bytes; if there are characters exceeding the BMP, each character occupies 4 bytes.
  • Go language's string type uses UTF-8 encoding internally. Go language also provides the rune type, which is used to represent a single Unicode code point.
  • Rust language's str and String types use UTF-8 encoding internally. Rust also provides the char type for representing a single Unicode code point.

It should be noted that the above discussion is about how strings are stored in programming languages, which is different from how strings are stored in files or transmitted over networks. In file storage or network transmission, we usually encode strings into UTF-8 format to achieve optimal compatibility and space efficiency.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/","level":1,"title":"3.1   Classification of Data Structures","text":"

Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be classified from two dimensions: \"logical structure\" and \"physical structure\".

","path":["Chapter 3. Data Structures","3.1   Classification of Data Structures"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/#311-logical-structure-linear-and-non-linear","level":2,"title":"3.1.1   Logical Structure: Linear and Non-Linear","text":"

Logical structure reveals the logical relationships between data elements. In arrays and linked lists, data is arranged in a certain order, embodying the linear relationship between data; while in trees, data is arranged hierarchically from top to bottom, showing the derived relationship between \"ancestors\" and \"descendants\"; graphs are composed of nodes and edges, reflecting complex network relationships.

As shown in Figure 3-1, logical structures can be divided into two major categories: \"linear\" and \"non-linear\". Linear structures are more intuitive, indicating that data is linearly arranged in logical relationships; non-linear structures are the opposite, arranged non-linearly.

  • Linear data structures: Arrays, linked lists, stacks, queues, hash tables, where elements have a one-to-one sequential relationship.
  • Non-linear data structures: Trees, heaps, graphs, hash tables.

Non-linear data structures can be further divided into tree structures and network structures.

  • Tree structures: Trees, heaps, hash tables, where elements have a one-to-many relationship.
  • Network structures: Graphs, where elements have a many-to-many relationship.

Figure 3-1   Linear and non-linear data structures

","path":["Chapter 3. Data Structures","3.1   Classification of Data Structures"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/#312-physical-structure-contiguous-and-dispersed","level":2,"title":"3.1.2   Physical Structure: Contiguous and Dispersed","text":"

When an algorithm program runs, the data being processed is mainly stored in memory. Figure 3-2 shows a computer memory stick, where each black square contains a memory space. We can imagine memory as a huge Excel spreadsheet, where each cell can store a certain amount of data.

The system accesses data at the target location through memory addresses. As shown in Figure 3-2, the computer assigns a number to each cell in the spreadsheet according to specific rules, ensuring that each memory space has a unique memory address. With these addresses, the program can access data in memory.

Figure 3-2   Memory stick, memory space, memory address

Tip

It is worth noting that comparing memory to an Excel spreadsheet is a simplified analogy. The actual working mechanism of memory is quite complex, involving concepts such as address space, memory management, cache mechanisms, virtual memory, and physical memory.

Memory is a shared resource for all programs. When a block of memory is occupied by a program, it usually cannot be used by other programs at the same time. Therefore, in the design of data structures and algorithms, memory resources are an important consideration. For example, the peak memory occupied by an algorithm should not exceed the remaining free memory of the system; if there is a lack of contiguous large memory blocks, then the data structure chosen must be able to be stored in dispersed memory spaces.

As shown in Figure 3-3, physical structure reflects the way data is stored in computer memory, and can be divided into contiguous space storage (arrays) and dispersed space storage (linked lists). The two physical structures exhibit complementary characteristics in terms of time efficiency and space efficiency.

Figure 3-3   Contiguous space storage and dispersed space storage

It is worth noting that all data structures are implemented based on arrays, linked lists, or a combination of both. For example, stacks and queues can be implemented using either arrays or linked lists; while the implementation of hash tables may include both arrays and linked lists.

  • Can be implemented based on arrays: Stacks, queues, hash tables, trees, heaps, graphs, matrices, tensors (arrays with dimensions \\(\\geq 3\\)), etc.
  • Can be implemented based on linked lists: Stacks, queues, hash tables, trees, heaps, graphs, etc.

After initialization, linked lists can still adjust their length during program execution, so they are also called \"dynamic data structures\". After initialization, the length of arrays cannot be changed, so they are also called \"static data structures\". It is worth noting that arrays can achieve length changes by reallocating memory, thus possessing a certain degree of \"dynamism\".

Tip

If you find it difficult to understand physical structure, it is recommended to read the next chapter first, and then review this section.

","path":["Chapter 3. Data Structures","3.1   Classification of Data Structures"],"tags":[]},{"location":"chapter_data_structure/number_encoding/","level":1,"title":"3.3   Number Encoding *","text":"

Tip

In this book, chapters marked with an asterisk * are optional readings. If you are short on time or find them challenging, you may skip these initially and return to them after completing the essential chapters.

","path":["Chapter 3. Data Structures","3.3   Number Encoding *"],"tags":[]},{"location":"chapter_data_structure/number_encoding/#331-sign-magnitude-1s-complement-and-2s-complement","level":2,"title":"3.3.1   Sign-Magnitude, 1's Complement, and 2's Complement","text":"

In the table from the previous section, we found that all integer types can represent one more negative number than positive numbers. For example, the byte range is \\([-128, 127]\\). This phenomenon is counterintuitive, and its underlying reason involves knowledge of sign-magnitude, 1's complement, and 2's complement.

First, it should be noted that numbers are stored in computers in the form of \"2's complement\". Before analyzing the reasons for this, let's first define these three concepts.

  • Sign-magnitude: We treat the highest bit of the binary representation of a number as the sign bit, where \\(0\\) represents a positive number and \\(1\\) represents a negative number, and the remaining bits represent the value of the number.
  • 1's complement: The 1's complement of a positive number is the same as its sign-magnitude. For a negative number, the 1's complement is obtained by inverting all bits except the sign bit of its sign-magnitude.
  • 2's complement: The 2's complement of a positive number is the same as its sign-magnitude. For a negative number, the 2's complement is obtained by adding \\(1\\) to its 1's complement.

Figure 3-4 shows the conversion methods among sign-magnitude, 1's complement, and 2's complement.

Figure 3-4   Conversions among sign-magnitude, 1's complement, and 2's complement

Sign-magnitude, although the most intuitive, has some limitations. On one hand, the sign-magnitude of negative numbers cannot be directly used in operations. For example, calculating \\(1 + (-2)\\) in sign-magnitude yields \\(-3\\), which is clearly incorrect.

\\[ \\begin{aligned} & 1 + (-2) \\newline & \\rightarrow 0000 \\; 0001 + 1000 \\; 0010 \\newline & = 1000 \\; 0011 \\newline & \\rightarrow -3 \\end{aligned} \\]

To solve this problem, computers introduced 1's complement. If we first convert sign-magnitude to 1's complement and calculate \\(1 + (-2)\\) in 1's complement, then convert the result back to sign-magnitude, we can obtain the correct result of \\(-1\\).

\\[ \\begin{aligned} & 1 + (-2) \\newline & \\rightarrow 0000 \\; 0001 \\; \\text{(Sign-magnitude)} + 1000 \\; 0010 \\; \\text{(Sign-magnitude)} \\newline & = 0000 \\; 0001 \\; \\text{(1's complement)} + 1111 \\; 1101 \\; \\text{(1's complement)} \\newline & = 1111 \\; 1110 \\; \\text{(1's complement)} \\newline & = 1000 \\; 0001 \\; \\text{(Sign-magnitude)} \\newline & \\rightarrow -1 \\end{aligned} \\]

On the other hand, the sign-magnitude of the number zero has two representations, \\(+0\\) and \\(-0\\). This means that the number zero corresponds to two different binary encodings, which may cause ambiguity. For example, in conditional judgments, if we don't distinguish between positive zero and negative zero, it may lead to incorrect judgment results. If we want to handle the ambiguity of positive and negative zero, we need to introduce additional judgment operations, which may reduce the computational efficiency of the computer.

\\[ \\begin{aligned} +0 & \\rightarrow 0000 \\; 0000 \\newline -0 & \\rightarrow 1000 \\; 0000 \\end{aligned} \\]

Like sign-magnitude, 1's complement also has the problem of positive and negative zero ambiguity. Therefore, computers further introduced 2's complement. Let's first observe the conversion process of negative zero from sign-magnitude to 1's complement to 2's complement:

\\[ \\begin{aligned} -0 \\rightarrow \\; & 1000 \\; 0000 \\; \\text{(Sign-magnitude)} \\newline = \\; & 1111 \\; 1111 \\; \\text{(1's complement)} \\newline = 1 \\; & 0000 \\; 0000 \\; \\text{(2's complement)} \\newline \\end{aligned} \\]

Adding \\(1\\) to the 1's complement of negative zero produces a carry, but since the byte type has a length of only 8 bits, the \\(1\\) that overflows to the 9th bit is discarded. That is to say, the 2's complement of negative zero is \\(0000 \\; 0000\\), which is the same as the 2's complement of positive zero. This means that in 2's complement representation, there is only one zero, and the positive and negative zero ambiguity is thus resolved.

One last question remains: the range of the byte type is \\([-128, 127]\\), and how is the extra negative number \\(-128\\) obtained? We notice that all integers in the interval \\([-127, +127]\\) have corresponding sign-magnitude, 1's complement, and 2's complement, and sign-magnitude and 2's complement can be converted to each other.

However, the 2's complement \\(1000 \\; 0000\\) is an exception, and it does not have a corresponding sign-magnitude. According to the conversion method, we get that the sign-magnitude of this 2's complement is \\(0000 \\; 0000\\). This is clearly contradictory because this sign-magnitude represents the number \\(0\\), and its 2's complement should be itself. The computer specifies that this special 2's complement \\(1000 \\; 0000\\) represents \\(-128\\). In fact, the result of calculating \\((-1) + (-127)\\) in 2's complement is \\(-128\\).

\\[ \\begin{aligned} & (-127) + (-1) \\newline & \\rightarrow 1111 \\; 1111 \\; \\text{(Sign-magnitude)} + 1000 \\; 0001 \\; \\text{(Sign-magnitude)} \\newline & = 1000 \\; 0000 \\; \\text{(1's complement)} + 1111 \\; 1110 \\; \\text{(1's complement)} \\newline & = 1000 \\; 0001 \\; \\text{(2's complement)} + 1111 \\; 1111 \\; \\text{(2's complement)} \\newline & = 1000 \\; 0000 \\; \\text{(2's complement)} \\newline & \\rightarrow -128 \\end{aligned} \\]

You may have noticed that all the above calculations are addition operations. This hints at an important fact: the hardware circuits inside computers are mainly designed based on addition operations. This is because addition operations are simpler to implement in hardware compared to other operations (such as multiplication, division, and subtraction), easier to parallelize, and have faster operation speeds.

Please note that this does not mean that computers can only perform addition. By combining addition with some basic logical operations, computers can implement various other mathematical operations. For example, calculating the subtraction \\(a - b\\) can be converted to calculating the addition \\(a + (-b)\\); calculating multiplication and division can be converted to calculating multiple additions or subtractions.

Now we can summarize the reasons why computers use 2's complement: based on 2's complement representation, computers can use the same circuits and operations to handle the addition of positive and negative numbers, without the need to design special hardware circuits to handle subtraction, and without the need to specially handle the ambiguity problem of positive and negative zero. This greatly simplifies hardware design and improves operational efficiency.

The design of 2's complement is very ingenious. Due to space limitations, we will stop here. Interested readers are encouraged to explore further.

","path":["Chapter 3. Data Structures","3.3   Number Encoding *"],"tags":[]},{"location":"chapter_data_structure/number_encoding/#332-floating-point-number-encoding","level":2,"title":"3.3.2   Floating-Point Number Encoding","text":"

Careful readers may have noticed: int and float have the same length, both are 4 bytes, but why does float have a much larger range than int? This is very counterintuitive because it stands to reason that float needs to represent decimals, so the range should be smaller.

In fact, this is because floating-point number float uses a different representation method. Let's denote a 32-bit binary number as:

\\[ b_{31} b_{30} b_{29} \\ldots b_2 b_1 b_0 \\]

According to the IEEE 754 standard, a 32-bit float consists of the following three parts.

  • Sign bit \\(\\mathrm{S}\\): occupies 1 bit, corresponding to \\(b_{31}\\).
  • Exponent bit \\(\\mathrm{E}\\): occupies 8 bits, corresponding to \\(b_{30} b_{29} \\ldots b_{23}\\).
  • Fraction bit \\(\\mathrm{N}\\): occupies 23 bits, corresponding to \\(b_{22} b_{21} \\ldots b_0\\).

The calculation method for the value corresponding to the binary float is:

\\[ \\text {val} = (-1)^{b_{31}} \\times 2^{\\left(b_{30} b_{29} \\ldots b_{23}\\right)_2-127} \\times\\left(1 . b_{22} b_{21} \\ldots b_0\\right)_2 \\]

Converted to decimal, the calculation formula is:

\\[ \\text {val}=(-1)^{\\mathrm{S}} \\times 2^{\\mathrm{E} -127} \\times (1 + \\mathrm{N}) \\]

The range of each component is:

\\[ \\begin{aligned} \\mathrm{S} \\in & \\{ 0, 1\\}, \\quad \\mathrm{E} \\in \\{ 1, 2, \\dots, 254 \\} \\newline (1 + \\mathrm{N}) = & (1 + \\sum_{i=1}^{23} b_{23-i} 2^{-i}) \\subset [1, 2 - 2^{-23}] \\end{aligned} \\]

Figure 3-5   Calculation example of float under IEEE 754 standard

Observing Figure 3-5, given example data \\(\\mathrm{S} = 0\\), \\(\\mathrm{E} = 124\\), \\(\\mathrm{N} = 2^{-2} + 2^{-3} = 0.375\\), we have:

\\[ \\text { val } = (-1)^0 \\times 2^{124 - 127} \\times (1 + 0.375) = 0.171875 \\]

Now we can answer the initial question: the representation of float includes an exponent bit, resulting in a range far greater than int. According to the above calculation, the maximum positive number that float can represent is \\(2^{254 - 127} \\times (2 - 2^{-23}) \\approx 3.4 \\times 10^{38}\\), and the minimum negative number can be obtained by switching the sign bit.

Although floating-point number float expands the range, its side effect is sacrificing precision. The integer type int uses all 32 bits to represent numbers, and the numbers are evenly distributed; however, due to the existence of the exponent bit, the larger the value of floating-point number float, the larger the difference between two adjacent numbers tends to be.

As shown in Table 3-2, exponent bits \\(\\mathrm{E} = 0\\) and \\(\\mathrm{E} = 255\\) have special meanings, used to represent zero, infinity, \\(\\mathrm{NaN}\\), etc.

Table 3-2   Meaning of exponent bits

Exponent Bit E Fraction Bit \\(\\mathrm{N} = 0\\) Fraction Bit \\(\\mathrm{N} \\ne 0\\) Calculation Formula \\(0\\) \\(\\pm 0\\) Subnormal Number \\((-1)^{\\mathrm{S}} \\times 2^{-126} \\times (0.\\mathrm{N})\\) \\(1, 2, \\dots, 254\\) Normal Number Normal Number \\((-1)^{\\mathrm{S}} \\times 2^{(\\mathrm{E} -127)} \\times (1.\\mathrm{N})\\) \\(255\\) \\(\\pm \\infty\\) \\(\\mathrm{NaN}\\)

It is worth noting that subnormal numbers significantly improve the precision of floating-point numbers. The smallest positive normal number is \\(2^{-126}\\), and the smallest positive subnormal number is \\(2^{-126} \\times 2^{-23}\\).

Double-precision double also uses a representation method similar to float, which will not be elaborated here.

","path":["Chapter 3. Data Structures","3.3   Number Encoding *"],"tags":[]},{"location":"chapter_data_structure/summary/","level":1,"title":"3.5   Summary","text":"","path":["Chapter 3. Data Structures","3.5   Summary"],"tags":[]},{"location":"chapter_data_structure/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Data structures can be classified from two perspectives: logical structure and physical structure. Logical structure describes the logical relationships between data elements, while physical structure describes how data is stored in computer memory.
  • Common logical structures include linear, tree, and network structures. We typically classify data structures as linear (arrays, linked lists, stacks, queues) and non-linear (trees, graphs, heaps) based on their logical structure. The implementation of hash tables may involve both linear and non-linear data structures.
  • When a program runs, data is stored in computer memory. Each memory space has a corresponding memory address, and the program accesses data through these memory addresses.
  • Physical structures are primarily divided into contiguous space storage (arrays) and dispersed space storage (linked lists). All data structures are implemented using arrays, linked lists, or a combination of both.
  • Basic data types in computers include integers byte, short, int, long, floating-point numbers float, double, characters char, and booleans bool. Their value ranges depend on the size of space they occupy and their representation method.
  • Sign-magnitude, 1's complement, and 2's complement are three methods for encoding numbers in computers, and they can be converted into each other. The most significant bit of sign-magnitude is the sign bit, and the remaining bits represent the value of the number.
  • Integers are stored in computers in 2's complement form. Under 2's complement representation, computers can treat the addition of positive and negative numbers uniformly, without needing to design special hardware circuits for subtraction, and there is no ambiguity of positive and negative zero.
  • The encoding of floating-point numbers consists of 1 sign bit, 8 exponent bits, and 23 fraction bits. Due to the exponent bits, the range of floating-point numbers is much larger than that of integers, at the cost of sacrificing precision.
  • ASCII is the earliest English character set, with a length of 1 byte, containing a total of 127 characters. GBK is a commonly used Chinese character set, containing over 20,000 Chinese characters. Unicode is committed to providing a complete character set standard, collecting characters from various languages around the world, thereby solving the garbled text problem caused by inconsistent character encoding methods.
  • UTF-8 is the most popular Unicode encoding method, with excellent universality. It is a variable-length encoding method with good scalability, effectively improving storage space efficiency. UTF-16 and UTF-32 are fixed-length encoding methods. When encoding Chinese characters, UTF-16 occupies less space than UTF-8. Programming languages such as Java and C# use UTF-16 encoding by default.
","path":["Chapter 3. Data Structures","3.5   Summary"],"tags":[]},{"location":"chapter_data_structure/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Why do hash tables contain both linear and non-linear data structures?

The underlying structure of a hash table is an array. To resolve hash collisions, we may use \"chaining\" (discussed in the subsequent \"Hash Collision\" section): each bucket in the array points to a linked list, which may be converted to a tree (usually a red-black tree) when the list length exceeds a certain threshold.

From a storage perspective, the underlying structure of a hash table is an array, where each bucket slot may contain a value, a linked list, or a tree. Therefore, hash tables may contain both linear data structures (arrays, linked lists) and non-linear data structures (trees).

Q: Is the length of the char type 1 byte?

The length of the char type is determined by the encoding method used by the programming language. For example, Java, JavaScript, TypeScript, and C# all use UTF-16 encoding (to store Unicode code points), so the char type has a length of 2 bytes.

Q: Is there ambiguity in referring to array-based data structures as \"static data structures\"? Stacks can also perform \"dynamic\" operations such as push and pop.

Stacks can indeed implement dynamic data operations, but the data structure is still \"static\" (fixed length). Although array-based data structures can dynamically add or remove elements, their capacity is fixed. If the data volume exceeds the pre-allocated size, a new larger array needs to be created, and the contents of the old array must be copied to the new array.

Q: When constructing a stack (queue), its size is not specified. Why are they \"static data structures\"?

In high-level programming languages, we do not need to manually specify the initial capacity of a stack (queue); this work is automatically completed within the class. For example, the initial capacity of Java's ArrayList is typically 10. Additionally, the expansion operation is also automatically implemented. See the subsequent \"List\" section for details.

Q: The method of converting sign-magnitude to 2's complement is \"first negate then add 1\". So converting 2's complement to sign-magnitude should be the inverse operation \"first subtract 1 then negate\". However, 2's complement can also be converted to sign-magnitude through \"first negate then add 1\". Why is this?

This is because the mutual conversion between sign-magnitude and 2's complement is actually the process of computing the \"complement\". Let us first define the complement: assuming \\(a + b = c\\), then we say that \\(a\\) is the complement of \\(b\\) to \\(c\\), and conversely, \\(b\\) is the complement of \\(a\\) to \\(c\\).

Given an \\(n = 4\\) bit binary number \\(0010\\), if we treat this number as sign-magnitude (ignoring the sign bit), then its 2's complement can be obtained through \"first negate then add 1\":

\\[ 0010 \\rightarrow 1101 \\rightarrow 1110 \\]

We find that the sum of sign-magnitude and 2's complement is \\(0010 + 1110 = 10000\\), which means the 2's complement \\(1110\\) is the \"complement\" of sign-magnitude \\(0010\\) to \\(10000\\). This means the above \"first negate then add 1\" is actually the process of computing the complement to \\(10000\\).

So, what is the \"complement\" of 2's complement \\(1110\\) to \\(10000\\)? We can still use \"first negate then add 1\" to obtain it:

\\[ 1110 \\rightarrow 0001 \\rightarrow 0010 \\]

In other words, sign-magnitude and 2's complement are each other's \"complement\" to \\(10000\\), so \"sign-magnitude to 2's complement\" and \"2's complement to sign-magnitude\" can be implemented using the same operation (first negate then add 1).

Of course, we can also use the inverse operation to find the sign-magnitude of 2's complement \\(1110\\), that is, \"first subtract 1 then negate\":

\\[ 1110 \\rightarrow 1101 \\rightarrow 0010 \\]

In summary, both \"first negate then add 1\" and \"first subtract 1 then negate\" are computing the complement to \\(10000\\), and they are equivalent.

Essentially, the \"negate\" operation is actually finding the complement to \\(1111\\) (because sign-magnitude + 1's complement = 1111 always holds); and adding 1 to the 1's complement yields the 2's complement, which is the complement to \\(10000\\).

The above uses \\(n = 4\\) as an example, and it can be generalized to binary numbers of any number of bits.

","path":["Chapter 3. Data Structures","3.5   Summary"],"tags":[]},{"location":"chapter_divide_and_conquer/","level":1,"title":"Chapter 12.   Divide and Conquer","text":"

Abstract

Difficult problems are decomposed layer by layer, with each decomposition making them simpler.

Divide and conquer reveals an important truth: start with simplicity, and nothing remains complex.

","path":["Chapter 12. Divide and Conquer","Chapter 12.   Divide and Conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 12.1   Divide and Conquer Algorithms
  • 12.2   Divide and Conquer Search Strategy
  • 12.3   Building a Binary Tree Problem
  • 12.4   Hanoi Tower Problem
  • 12.5   Summary
","path":["Chapter 12. Divide and Conquer","Chapter 12.   Divide and Conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/binary_search_recur/","level":1,"title":"12.2   Divide and Conquer Search Strategy","text":"

We have already learned that search algorithms are divided into two major categories.

  • Brute-force search: Implemented by traversing the data structure, with a time complexity of \\(O(n)\\).
  • Adaptive search: Utilizes unique data organization forms or prior information, with time complexity reaching \\(O(\\log n)\\) or even \\(O(1)\\).

In fact, search algorithms with time complexity of \\(O(\\log n)\\) are typically implemented based on the divide and conquer strategy, such as binary search and trees.

  • Each step of binary search divides the problem (searching for a target element in an array) into a smaller problem (searching for the target element in half of the array), continuing until the array is empty or the target element is found.
  • Trees are representative of the divide and conquer idea. In data structures such as binary search trees, AVL trees, and heaps, the time complexity of various operations is \\(O(\\log n)\\).

The divide and conquer strategy of binary search is as follows.

  • The problem can be decomposed: Binary search recursively decomposes the original problem (searching in an array) into subproblems (searching in half of the array), achieved by comparing the middle element with the target element.
  • Subproblems are independent: In binary search, each round only processes one subproblem, which is not affected by other subproblems.
  • Solutions of subproblems do not need to be merged: Binary search aims to find a specific element, so there is no need to merge the solutions of subproblems. When a subproblem is solved, the original problem is also solved.

Divide and conquer can improve search efficiency because brute-force search can only eliminate one option per round, while divide and conquer search can eliminate half of the options per round.

","path":["Chapter 12. Divide and Conquer","12.2   Divide and Conquer Search Strategy"],"tags":[]},{"location":"chapter_divide_and_conquer/binary_search_recur/#1-implementing-binary-search-based-on-divide-and-conquer","level":3,"title":"1.   Implementing Binary Search Based on Divide and Conquer","text":"

In previous sections, binary search was implemented based on iteration. Now we implement it based on divide and conquer (recursion).

Question

Given a sorted array nums of length \\(n\\), where all elements are unique, find the element target.

From a divide and conquer perspective, we denote the subproblem corresponding to the search interval \\([i, j]\\) as \\(f(i, j)\\).

Starting from the original problem \\(f(0, n-1)\\), perform binary search through the following steps.

  1. Calculate the midpoint \\(m\\) of the search interval \\([i, j]\\), and use it to eliminate half of the search interval.
  2. Recursively solve the subproblem reduced by half in size, which could be \\(f(i, m-1)\\) or \\(f(m+1, j)\\).
  3. Repeat steps 1. and 2. until target is found or the interval is empty and return.

Figure 12-4 shows the divide and conquer process of binary search for element \\(6\\) in an array.

Figure 12-4   Divide and conquer process of binary search

In the implementation code, we declare a recursive function dfs() to solve the problem \\(f(i, j)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_recur.py
def dfs(nums: list[int], target: int, i: int, j: int) -> int:\n    \"\"\"Binary search: problem f(i, j)\"\"\"\n    # If the interval is empty, it means there is no target element, return -1\n    if i > j:\n        return -1\n    # Calculate the midpoint index m\n    m = (i + j) // 2\n    if nums[m] < target:\n        # Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j)\n    elif nums[m] > target:\n        # Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1)\n    else:\n        # Found the target element, return its index\n        return m\n\ndef binary_search(nums: list[int], target: int) -> int:\n    \"\"\"Binary search\"\"\"\n    n = len(nums)\n    # Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1)\n
binary_search_recur.cpp
/* Binary search: problem f(i, j) */\nint dfs(vector<int> &nums, int target, int i, int j) {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(vector<int> &nums, int target) {\n    int n = nums.size();\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.java
/* Binary search: problem f(i, j) */\nint dfs(int[] nums, int target, int i, int j) {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(int[] nums, int target) {\n    int n = nums.length;\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.cs
/* Binary search: problem f(i, j) */\nint DFS(int[] nums, int target, int i, int j) {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return DFS(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return DFS(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint BinarySearch(int[] nums, int target) {\n    int n = nums.Length;\n    // Solve the problem f(0, n-1)\n    return DFS(nums, target, 0, n - 1);\n}\n
binary_search_recur.go
/* Binary search: problem f(i, j) */\nfunc dfs(nums []int, target, i, j int) int {\n    // If interval is empty, indicating no target element, return -1\n    if i > j {\n        return -1\n    }\n    // Calculate midpoint index\n    m := i + ((j - i) >> 1)\n    // Compare midpoint with target element\n    if nums[m] < target {\n        // If smaller, recurse on right half of array\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m+1, j)\n    } else if nums[m] > target {\n        // If larger, recurse on left half of array\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m-1)\n    } else {\n        // Found the target element, return its index\n        return m\n    }\n}\n\n/* Binary search */\nfunc binarySearch(nums []int, target int) int {\n    n := len(nums)\n    return dfs(nums, target, 0, n-1)\n}\n
binary_search_recur.swift
/* Binary search: problem f(i, j) */\nfunc dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int {\n    // If the interval is empty, it means there is no target element, return -1\n    if i > j {\n        return -1\n    }\n    // Calculate the midpoint index m\n    let m = (i + j) / 2\n    if nums[m] < target {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums: nums, target: target, i: m + 1, j: j)\n    } else if nums[m] > target {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums: nums, target: target, i: i, j: m - 1)\n    } else {\n        // Found the target element, return its index\n        return m\n    }\n}\n\n/* Binary search */\nfunc binarySearch(nums: [Int], target: Int) -> Int {\n    // Solve the problem f(0, n-1)\n    dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1)\n}\n
binary_search_recur.js
/* Binary search: problem f(i, j) */\nfunction dfs(nums, target, i, j) {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    const m = i + ((j - i) >> 1);\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfunction binarySearch(nums, target) {\n    const n = nums.length;\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.ts
/* Binary search: problem f(i, j) */\nfunction dfs(nums: number[], target: number, i: number, j: number): number {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    const m = i + ((j - i) >> 1);\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfunction binarySearch(nums: number[], target: number): number {\n    const n = nums.length;\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.dart
/* Binary search: problem f(i, j) */\nint dfs(List<int> nums, int target, int i, int j) {\n  // If the interval is empty, it means there is no target element, return -1\n  if (i > j) {\n    return -1;\n  }\n  // Calculate the midpoint index m\n  int m = (i + j) ~/ 2;\n  if (nums[m] < target) {\n    // Recursion subproblem f(m+1, j)\n    return dfs(nums, target, m + 1, j);\n  } else if (nums[m] > target) {\n    // Recursion subproblem f(i, m-1)\n    return dfs(nums, target, i, m - 1);\n  } else {\n    // Found the target element, return its index\n    return m;\n  }\n}\n\n/* Binary search */\nint binarySearch(List<int> nums, int target) {\n  int n = nums.length;\n  // Solve the problem f(0, n-1)\n  return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.rs
/* Binary search: problem f(i, j) */\nfn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 {\n    // If the interval is empty, it means there is no target element, return -1\n    if i > j {\n        return -1;\n    }\n    let m: i32 = i + (j - i) / 2;\n    if nums[m as usize] < target {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if nums[m as usize] > target {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfn binary_search(nums: &[i32], target: i32) -> i32 {\n    let n = nums.len() as i32;\n    // Solve the problem f(0, n-1)\n    dfs(nums, target, 0, n - 1)\n}\n
binary_search_recur.c
/* Binary search: problem f(i, j) */\nint dfs(int nums[], int target, int i, int j) {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(int nums[], int target, int numsSize) {\n    int n = numsSize;\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.kt
/* Binary search: problem f(i, j) */\nfun dfs(\n    nums: IntArray,\n    target: Int,\n    i: Int,\n    j: Int\n): Int {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1\n    }\n    // Calculate the midpoint index m\n    val m = (i + j) / 2\n    return if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        dfs(nums, target, m + 1, j)\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        dfs(nums, target, i, m - 1)\n    } else {\n        // Found the target element, return its index\n        m\n    }\n}\n\n/* Binary search */\nfun binarySearch(nums: IntArray, target: Int): Int {\n    val n = nums.size\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1)\n}\n
binary_search_recur.rb
### Binary search: problem f(i, j) ###\ndef dfs(nums, target, i, j)\n  # If the interval is empty, it means there is no target element, return -1\n  return -1 if i > j\n\n  # Calculate the midpoint index m\n  m = (i + j) / 2\n\n  if nums[m] < target\n    # Recursion subproblem f(m+1, j)\n    return dfs(nums, target, m + 1, j)\n  elsif nums[m] > target\n    # Recursion subproblem f(i, m-1)\n    return dfs(nums, target, i, m - 1)\n  else\n    # Found the target element, return its index\n    return m\n  end\nend\n\n### Binary search ###\ndef binary_search(nums, target)\n  n = nums.length\n  # Solve the problem f(0, n-1)\n  dfs(nums, target, 0, n - 1)\nend\n
","path":["Chapter 12. Divide and Conquer","12.2   Divide and Conquer Search Strategy"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/","level":1,"title":"12.3   Building a Binary Tree Problem","text":"

Question

Given the preorder traversal preorder and inorder traversal inorder of a binary tree, construct the binary tree and return the root node of the binary tree. Assume there are no duplicate node values in the binary tree (as shown in Figure 12-5).

Figure 12-5   Example data for building a binary tree

","path":["Chapter 12. Divide and Conquer","12.3   Building a Binary Tree Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#1-determining-if-it-is-a-divide-and-conquer-problem","level":3,"title":"1.   Determining If It Is a Divide and Conquer Problem","text":"

The original problem is defined as constructing a binary tree from preorder and inorder, which is a typical divide and conquer problem.

  • The problem can be decomposed: From a divide and conquer perspective, we can divide the original problem into two subproblems: constructing the left subtree and constructing the right subtree, plus one operation: initializing the root node. For each subtree (subproblem), we can still reuse the above division method, dividing it into smaller subtrees (subproblems) until the smallest subproblem (empty subtree) is reached.
  • Subproblems are independent: The left and right subtrees are independent of each other; there is no overlap between them. When constructing the left subtree, we only need to focus on the parts of the inorder and preorder traversals corresponding to the left subtree. The same applies to the right subtree.
  • Solutions of subproblems can be merged: Once we have the left and right subtrees (solutions of subproblems), we can link them to the root node to obtain the solution to the original problem.
","path":["Chapter 12. Divide and Conquer","12.3   Building a Binary Tree Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#2-how-to-divide-subtrees","level":3,"title":"2.   How to Divide Subtrees","text":"

Based on the above analysis, this problem can be solved using divide and conquer, but how do we divide the left and right subtrees through the preorder traversal preorder and inorder traversal inorder?

According to the definition, both preorder and inorder can be divided into three parts.

  • Preorder traversal: [ Root Node | Left Subtree | Right Subtree ], for example, the tree in Figure 12-5 corresponds to [ 3 | 9 | 2 1 7 ].
  • Inorder traversal: [ Left Subtree | Root Node | Right Subtree ], for example, the tree in Figure 12-5 corresponds to [ 9 | 3 | 1 2 7 ].

Using the data from the figure above as an example, we can obtain the division results through the steps shown in Figure 12-6.

  1. The first element 3 in the preorder traversal is the value of the root node.
  2. Find the index of root node 3 in inorder, and use this index to divide inorder into [ 9 | 3 | 1 2 7 ].
  3. Based on the division result of inorder, it is easy to determine that the left and right subtrees have 1 and 3 nodes respectively, allowing us to divide preorder into [ 3 | 9 | 2 1 7 ].

Figure 12-6   Dividing subtrees in preorder and inorder traversals

","path":["Chapter 12. Divide and Conquer","12.3   Building a Binary Tree Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#3-describing-subtree-intervals-based-on-variables","level":3,"title":"3.   Describing Subtree Intervals Based on Variables","text":"

Based on the above division method, we have obtained the index intervals of the root node, left subtree, and right subtree in preorder and inorder. To describe these index intervals, we need to use several pointer variables.

  • Denote the index of the current tree's root node in preorder as \\(i\\).
  • Denote the index of the current tree's root node in inorder as \\(m\\).
  • Denote the index interval of the current tree in inorder as \\([l, r]\\).

As shown in Table 12-1, through these variables we can represent the index of the root node in preorder and the index intervals of the subtrees in inorder.

Table 12-1   Indices of root node and subtrees in preorder and inorder traversals

Root node index in preorder Subtree index interval in inorder Current tree \\(i\\) \\([l, r]\\) Left subtree \\(i + 1\\) \\([l, m-1]\\) Right subtree \\(i + 1 + (m - l)\\) \\([m+1, r]\\)

Please note that \\((m-l)\\) in the right subtree root node index means \"the number of nodes in the left subtree\". It is recommended to understand this in conjunction with Figure 12-7.

Figure 12-7   Index interval representation of root node and left and right subtrees

","path":["Chapter 12. Divide and Conquer","12.3   Building a Binary Tree Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#4-code-implementation","level":3,"title":"4.   Code Implementation","text":"

To improve the efficiency of querying \\(m\\), we use a hash table hmap to store the mapping from elements in the inorder array to their indices:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby build_tree.py
def dfs(\n    preorder: list[int],\n    inorder_map: dict[int, int],\n    i: int,\n    l: int,\n    r: int,\n) -> TreeNode | None:\n    \"\"\"Build binary tree: divide and conquer\"\"\"\n    # Terminate when the subtree interval is empty\n    if r - l < 0:\n        return None\n    # Initialize the root node\n    root = TreeNode(preorder[i])\n    # Query m to divide the left and right subtrees\n    m = inorder_map[preorder[i]]\n    # Subproblem: build the left subtree\n    root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)\n    # Subproblem: build the right subtree\n    root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)\n    # Return the root node\n    return root\n\ndef build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None:\n    \"\"\"Build binary tree\"\"\"\n    # Initialize hash map, storing the mapping from inorder elements to indices\n    inorder_map = {val: i for i, val in enumerate(inorder)}\n    root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1)\n    return root\n
build_tree.cpp
/* Build binary tree: divide and conquer */\nTreeNode *dfs(vector<int> &preorder, unordered_map<int, int> &inorderMap, int i, int l, int r) {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0)\n        return NULL;\n    // Initialize the root node\n    TreeNode *root = new TreeNode(preorder[i]);\n    // Query m to divide the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root->left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nTreeNode *buildTree(vector<int> &preorder, vector<int> &inorder) {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    unordered_map<int, int> inorderMap;\n    for (int i = 0; i < inorder.size(); i++) {\n        inorderMap[inorder[i]] = i;\n    }\n    TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1);\n    return root;\n}\n
build_tree.java
/* Build binary tree: divide and conquer */\nTreeNode dfs(int[] preorder, Map<Integer, Integer> inorderMap, int i, int l, int r) {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0)\n        return null;\n    // Initialize the root node\n    TreeNode root = new TreeNode(preorder[i]);\n    // Query m to divide the left and right subtrees\n    int m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nTreeNode buildTree(int[] preorder, int[] inorder) {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    Map<Integer, Integer> inorderMap = new HashMap<>();\n    for (int i = 0; i < inorder.length; i++) {\n        inorderMap.put(inorder[i], i);\n    }\n    TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
build_tree.cs
/* Build binary tree: divide and conquer */\nTreeNode? DFS(int[] preorder, Dictionary<int, int> inorderMap, int i, int l, int r) {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0)\n        return null;\n    // Initialize the root node\n    TreeNode root = new(preorder[i]);\n    // Query m to divide the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root.left = DFS(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nTreeNode? BuildTree(int[] preorder, int[] inorder) {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    Dictionary<int, int> inorderMap = [];\n    for (int i = 0; i < inorder.Length; i++) {\n        inorderMap.TryAdd(inorder[i], i);\n    }\n    TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1);\n    return root;\n}\n
build_tree.go
/* Build binary tree: divide and conquer */\nfunc dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode {\n    // Terminate when the subtree interval is empty\n    if r-l < 0 {\n        return nil\n    }\n    // Initialize the root node\n    root := NewTreeNode(preorder[i])\n    // Query m to divide the left and right subtrees\n    m := inorderMap[preorder[i]]\n    // Subproblem: build the left subtree\n    root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1)\n    // Subproblem: build the right subtree\n    root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r)\n    // Return the root node\n    return root\n}\n\n/* Build binary tree */\nfunc buildTree(preorder, inorder []int) *TreeNode {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    inorderMap := make(map[int]int, len(inorder))\n    for i := 0; i < len(inorder); i++ {\n        inorderMap[inorder[i]] = i\n    }\n\n    root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1)\n    return root\n}\n
build_tree.swift
/* Build binary tree: divide and conquer */\nfunc dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? {\n    // Terminate when the subtree interval is empty\n    if r - l < 0 {\n        return nil\n    }\n    // Initialize the root node\n    let root = TreeNode(x: preorder[i])\n    // Query m to divide the left and right subtrees\n    let m = inorderMap[preorder[i]]!\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1)\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r)\n    // Return the root node\n    return root\n}\n\n/* Build binary tree */\nfunc buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }\n    return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1)\n}\n
build_tree.js
/* Build binary tree: divide and conquer */\nfunction dfs(preorder, inorderMap, i, l, r) {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0) return null;\n    // Initialize the root node\n    const root = new TreeNode(preorder[i]);\n    // Query m to divide the left and right subtrees\n    const m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nfunction buildTree(preorder, inorder) {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    let inorderMap = new Map();\n    for (let i = 0; i < inorder.length; i++) {\n        inorderMap.set(inorder[i], i);\n    }\n    const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
build_tree.ts
/* Build binary tree: divide and conquer */\nfunction dfs(\n    preorder: number[],\n    inorderMap: Map<number, number>,\n    i: number,\n    l: number,\n    r: number\n): TreeNode | null {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0) return null;\n    // Initialize the root node\n    const root: TreeNode = new TreeNode(preorder[i]);\n    // Query m to divide the left and right subtrees\n    const m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nfunction buildTree(preorder: number[], inorder: number[]): TreeNode | null {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    let inorderMap = new Map<number, number>();\n    for (let i = 0; i < inorder.length; i++) {\n        inorderMap.set(inorder[i], i);\n    }\n    const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
build_tree.dart
/* Build binary tree: divide and conquer */\nTreeNode? dfs(\n  List<int> preorder,\n  Map<int, int> inorderMap,\n  int i,\n  int l,\n  int r,\n) {\n  // Terminate when the subtree interval is empty\n  if (r - l < 0) {\n    return null;\n  }\n  // Initialize the root node\n  TreeNode? root = TreeNode(preorder[i]);\n  // Query m to divide the left and right subtrees\n  int m = inorderMap[preorder[i]]!;\n  // Subproblem: build the left subtree\n  root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n  // Subproblem: build the right subtree\n  root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n  // Return the root node\n  return root;\n}\n\n/* Build binary tree */\nTreeNode? buildTree(List<int> preorder, List<int> inorder) {\n  // Initialize hash map, storing the mapping from inorder elements to indices\n  Map<int, int> inorderMap = {};\n  for (int i = 0; i < inorder.length; i++) {\n    inorderMap[inorder[i]] = i;\n  }\n  TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n  return root;\n}\n
build_tree.rs
/* Build binary tree: divide and conquer */\nfn dfs(\n    preorder: &[i32],\n    inorder_map: &HashMap<i32, i32>,\n    i: i32,\n    l: i32,\n    r: i32,\n) -> Option<Rc<RefCell<TreeNode>>> {\n    // Terminate when the subtree interval is empty\n    if r - l < 0 {\n        return None;\n    }\n    // Initialize the root node\n    let root = TreeNode::new(preorder[i as usize]);\n    // Query m to divide the left and right subtrees\n    let m = inorder_map.get(&preorder[i as usize]).unwrap();\n    // Subproblem: build the left subtree\n    root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    Some(root)\n}\n\n/* Build binary tree */\nfn build_tree(preorder: &[i32], inorder: &[i32]) -> Option<Rc<RefCell<TreeNode>>> {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    let mut inorder_map: HashMap<i32, i32> = HashMap::new();\n    for i in 0..inorder.len() {\n        inorder_map.insert(inorder[i], i as i32);\n    }\n    let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1);\n    root\n}\n
build_tree.c
/* Build binary tree: divide and conquer */\nTreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0)\n        return NULL;\n    // Initialize the root node\n    TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode));\n    root->val = preorder[i];\n    root->left = NULL;\n    root->right = NULL;\n    // Query m to divide the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size);\n    // Subproblem: build the right subtree\n    root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nTreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE);\n    for (int i = 0; i < inorderSize; i++) {\n        inorderMap[inorder[i]] = i;\n    }\n    TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize);\n    free(inorderMap);\n    return root;\n}\n
build_tree.kt
/* Build binary tree: divide and conquer */\nfun dfs(\n    preorder: IntArray,\n    inorderMap: Map<Int?, Int?>,\n    i: Int,\n    l: Int,\n    r: Int\n): TreeNode? {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0) return null\n    // Initialize the root node\n    val root = TreeNode(preorder[i])\n    // Query m to divide the left and right subtrees\n    val m = inorderMap[preorder[i]]!!\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1)\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r)\n    // Return the root node\n    return root\n}\n\n/* Build binary tree */\nfun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    val inorderMap = HashMap<Int?, Int?>()\n    for (i in inorder.indices) {\n        inorderMap[inorder[i]] = i\n    }\n    val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1)\n    return root\n}\n
build_tree.rb
### Build binary tree: divide and conquer ###\ndef dfs(preorder, inorder_map, i, l, r)\n  # Terminate when the subtree interval is empty\n  return if r - l < 0\n\n  # Initialize the root node\n  root = TreeNode.new(preorder[i])\n  # Query m to divide the left and right subtrees\n  m = inorder_map[preorder[i]]\n  # Subproblem: build the left subtree\n  root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)\n  # Subproblem: build the right subtree\n  root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)\n\n  # Return the root node\n  root\nend\n\n### Build binary tree ###\ndef build_tree(preorder, inorder)\n  # Initialize hash map, storing the mapping from inorder elements to indices\n  inorder_map = {}\n  inorder.each_with_index { |val, i| inorder_map[val] = i }\n  dfs(preorder, inorder_map, 0, 0, inorder.length - 1)\nend\n

Figure 12-8 shows the recursive process of building the binary tree. Each node is established during the downward \"recursion\" process, while each edge (reference) is established during the upward \"return\" process.

<1><2><3><4><5><6><7><8><9>

Figure 12-8   Recursive process of building a binary tree

The division results of the preorder traversal preorder and inorder traversal inorder within each recursive function are shown in Figure 12-9.

Figure 12-9   Division results in each recursive function

Let the number of nodes in the tree be \\(n\\). Initializing each node (executing one recursive function dfs()) takes \\(O(1)\\) time. Therefore, the overall time complexity is \\(O(n)\\).

The hash table stores the mapping from inorder elements to their indices, with a space complexity of \\(O(n)\\). In the worst case, when the binary tree degenerates into a linked list, the recursion depth reaches \\(n\\), using \\(O(n)\\) stack frame space. Therefore, the overall space complexity is \\(O(n)\\).

","path":["Chapter 12. Divide and Conquer","12.3   Building a Binary Tree Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/","level":1,"title":"12.1   Divide and Conquer Algorithms","text":"

Divide and conquer is a very important and common algorithm strategy. Divide and conquer is typically implemented based on recursion, consisting of two steps: \"divide\" and \"conquer\".

  1. Divide (partition phase): Recursively divide the original problem into two or more subproblems until the smallest subproblem is reached.
  2. Conquer (merge phase): Starting from the smallest subproblems with known solutions, merge the solutions of subproblems from bottom to top to construct the solution to the original problem.

As shown in Figure 12-1, \"merge sort\" is one of the typical applications of the divide and conquer strategy.

  1. Divide: Recursively divide the original array (original problem) into two subarrays (subproblems) until the subarray has only one element (smallest subproblem).
  2. Conquer: Merge the sorted subarrays (solutions to subproblems) from bottom to top to obtain a sorted original array (solution to the original problem).

Figure 12-1   Divide and conquer strategy of merge sort

","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1211-how-to-determine-divide-and-conquer-problems","level":2,"title":"12.1.1   How to Determine Divide and Conquer Problems","text":"

Whether a problem is suitable for solving with divide and conquer can usually be determined based on the following criteria.

  1. The problem can be decomposed: The original problem can be divided into smaller, similar subproblems, and can be recursively divided in the same way.
  2. Subproblems are independent: There is no overlap between subproblems, they are independent of each other and can be solved independently.
  3. Solutions of subproblems can be merged: The solution to the original problem is obtained by merging the solutions of subproblems.

Clearly, merge sort satisfies these three criteria.

  1. The problem can be decomposed: Recursively divide the array (original problem) into two subarrays (subproblems).
  2. Subproblems are independent: Each subarray can be sorted independently (subproblems can be solved independently).
  3. Solutions of subproblems can be merged: Two sorted subarrays (solutions of subproblems) can be merged into one sorted array (solution of the original problem).
","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1212-improving-efficiency-through-divide-and-conquer","level":2,"title":"12.1.2   Improving Efficiency Through Divide and Conquer","text":"

Divide and conquer can not only effectively solve algorithmic problems but often also improve algorithm efficiency. In sorting algorithms, quick sort, merge sort, and heap sort are faster than selection, bubble, and insertion sort because they apply the divide and conquer strategy.

This raises the question: Why can divide and conquer improve algorithm efficiency, and what is the underlying logic? In other words, why is dividing a large problem into multiple subproblems, solving the subproblems, and merging their solutions more efficient than directly solving the original problem? This question can be discussed from two aspects: operation count and parallel computation.

","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1-operation-count-optimization","level":3,"title":"1.   Operation Count Optimization","text":"

Taking \"bubble sort\" as an example, processing an array of length \\(n\\) requires \\(O(n^2)\\) time. Suppose we divide the array into two subarrays from the midpoint as shown in Figure 12-2, the division requires \\(O(n)\\) time, sorting each subarray requires \\(O((n / 2)^2)\\) time, and merging the two subarrays requires \\(O(n)\\) time, resulting in an overall time complexity of:

\\[ O(n + (\\frac{n}{2})^2 \\times 2 + n) = O(\\frac{n^2}{2} + 2n) \\]

Figure 12-2   Bubble sort before and after array division

Next, we compute the following inequality, where the left and right sides represent the total number of operations before and after division, respectively:

\\[ \\begin{aligned} n^2 & > \\frac{n^2}{2} + 2n \\newline n^2 - \\frac{n^2}{2} - 2n & > 0 \\newline n(n - 4) & > 0 \\end{aligned} \\]

This means that when \\(n > 4\\), the number of operations after division is smaller, and sorting efficiency should be higher. Note that the time complexity after division is still quadratic \\(O(n^2)\\), but the constant term in the complexity has become smaller.

Going further, what if we continuously divide the subarrays from their midpoints into two subarrays until the subarrays have only one element? This approach is actually \"merge sort\", with a time complexity of \\(O(n \\log n)\\).

Thinking further, what if we set multiple division points and evenly divide the original array into \\(k\\) subarrays? This situation is very similar to \"bucket sort\", which is well-suited for sorting massive amounts of data, with a theoretical time complexity of \\(O(n + k)\\).

","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#2-parallel-computation-optimization","level":3,"title":"2.   Parallel Computation Optimization","text":"

We know that the subproblems generated by divide and conquer are independent of each other, so they can typically be solved in parallel. This means divide and conquer can not only reduce the time complexity of algorithms, but also benefits from parallel optimization by operating systems.

Parallel optimization is particularly effective in multi-core or multi-processor environments, as the system can simultaneously handle multiple subproblems, making fuller use of computing resources and significantly reducing overall runtime.

For example, in the \"bucket sort\" shown in Figure 12-3, we evenly distribute massive data into various buckets, and the sorting tasks for all buckets can be distributed to various computing units. After completion, the results are merged.

Figure 12-3   Parallel computation in bucket sort

","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1213-common-applications-of-divide-and-conquer","level":2,"title":"12.1.3   Common Applications of Divide and Conquer","text":"

On one hand, divide and conquer can be used to solve many classic algorithmic problems.

  • Finding the closest pair of points: This algorithm first divides the point set into two parts, then finds the closest pair of points in each part separately, and finally finds the closest pair of points that spans both parts.
  • Large integer multiplication: For example, the Karatsuba algorithm, which decomposes large integer multiplication into several smaller integer multiplications and additions.
  • Matrix multiplication: For example, the Strassen algorithm, which decomposes large matrix multiplication into multiple small matrix multiplications and additions.
  • Hanota problem: The hanota problem can be solved through recursion, which is a typical application of the divide and conquer strategy.
  • Solving inversion pairs: In a sequence, if a preceding number is greater than a following number, these two numbers form an inversion pair. Solving the inversion pair problem can utilize the divide and conquer approach with the help of merge sort.

On the other hand, divide and conquer is widely applied in the design of algorithms and data structures.

  • Binary search: Binary search divides a sorted array into two parts from the midpoint index, then decides which half to eliminate based on the comparison result between the target value and the middle element value, and performs the same binary operation on the remaining interval.
  • Merge sort: Already introduced at the beginning of this section, no further elaboration needed.
  • Quick sort: Quick sort selects a pivot value, then divides the array into two subarrays, one with elements smaller than the pivot and the other with elements larger than the pivot, then performs the same division operation on these two parts until the subarrays have only one element.
  • Bucket sort: The basic idea of bucket sort is to scatter data into multiple buckets, then sort the elements within each bucket, and finally extract the elements from each bucket in sequence to obtain a sorted array.
  • Trees: For example, binary search trees, AVL trees, red-black trees, B-trees, B+ trees, etc. Their search, insertion, and deletion operations can all be viewed as applications of the divide and conquer strategy.
  • Heaps: A heap is a special complete binary tree, and its various operations, such as insertion, deletion, and heapify, actually imply the divide and conquer idea.
  • Hash tables: Although hash tables do not directly apply divide and conquer, some hash collision resolution solutions indirectly apply the divide and conquer strategy. For example, long linked lists in chaining may be converted to red-black trees to improve query efficiency.

It can be seen that divide and conquer is a \"subtly pervasive\" algorithmic idea, embedded in various algorithms and data structures.

","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/","level":1,"title":"12.4   Hanota Problem","text":"

In merge sort and building binary trees, we decompose the original problem into two subproblems, each half the size of the original problem. However, for the hanota problem, we adopt a different decomposition strategy.

Question

Given three pillars, denoted as A, B, and C. Initially, pillar A has \\(n\\) discs stacked on it, arranged from top to bottom in ascending order of size. Our task is to move these \\(n\\) discs to pillar C while maintaining their original order (as shown in Figure 12-10). The following rules must be followed when moving the discs.

  1. A disc can only be taken from the top of one pillar and placed on top of another pillar.
  2. Only one disc can be moved at a time.
  3. A smaller disc must always be on top of a larger disc.

Figure 12-10   Example of the hanota problem

We denote the hanota problem of size \\(i\\) as \\(f(i)\\). For example, \\(f(3)\\) represents moving \\(3\\) discs from A to C.

","path":["Chapter 12. Divide and Conquer","12.4   Hanota Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#1-considering-the-base-cases","level":3,"title":"1.   Considering the Base Cases","text":"

As shown in Figure 12-11, for problem \\(f(1)\\), when there is only one disc, we can move it directly from A to C.

<1><2>

Figure 12-11   Solution for a problem of size 1

As shown in Figure 12-12, for problem \\(f(2)\\), when there are two discs, since we must always keep the smaller disc on top of the larger disc, we need to use B to assist in the move.

  1. First, move the smaller disc from A to B.
  2. Then move the larger disc from A to C.
  3. Finally, move the smaller disc from B to C.
<1><2><3><4>

Figure 12-12   Solution for a problem of size 2

The process of solving problem \\(f(2)\\) can be summarized as: moving two discs from A to C with the help of B. Here, C is called the target pillar, and B is called the buffer pillar.

","path":["Chapter 12. Divide and Conquer","12.4   Hanota Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#2-subproblem-decomposition","level":3,"title":"2.   Subproblem Decomposition","text":"

For problem \\(f(3)\\), when there are three discs, the situation becomes slightly more complex.

Since we already know the solutions to \\(f(1)\\) and \\(f(2)\\), we can think from a divide and conquer perspective, treating the top two discs on A as a whole, and execute the steps shown in Figure 12-13. This successfully moves the three discs from A to C.

  1. Let B be the target pillar and C be the buffer pillar, and move two discs from A to B.
  2. Move the remaining disc from A directly to C.
  3. Let C be the target pillar and A be the buffer pillar, and move two discs from B to C.
<1><2><3><4>

Figure 12-13   Solution for a problem of size 3

Essentially, we divide problem \\(f(3)\\) into two subproblems \\(f(2)\\) and one subproblem \\(f(1)\\). By solving these three subproblems in order, the original problem is solved. This shows that the subproblems are independent and their solutions can be merged.

From this, we can summarize the divide and conquer strategy for solving the hanota problem shown in Figure 12-14: divide the original problem \\(f(n)\\) into two subproblems \\(f(n-1)\\) and one subproblem \\(f(1)\\), and solve these three subproblems in the following order.

  1. Move \\(n-1\\) discs from A to B with the help of C.
  2. Move the remaining \\(1\\) disc directly from A to C.
  3. Move \\(n-1\\) discs from B to C with the help of A.

For these two subproblems \\(f(n-1)\\), we can recursively divide them in the same way until reaching the smallest subproblem \\(f(1)\\). The solution to \\(f(1)\\) is known and requires only one move operation.

Figure 12-14   Divide and conquer strategy for solving the hanota problem

","path":["Chapter 12. Divide and Conquer","12.4   Hanota Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#3-code-implementation","level":3,"title":"3.   Code Implementation","text":"

In the code, we declare a recursive function dfs(i, src, buf, tar), whose purpose is to move the top \\(i\\) discs from pillar src to target pillar tar with the help of buffer pillar buf:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hanota.py
def move(src: list[int], tar: list[int]):\n    \"\"\"Move a disk\"\"\"\n    # Take out a disk from the top of src\n    pan = src.pop()\n    # Place the disk on top of tar\n    tar.append(pan)\n\ndef dfs(i: int, src: list[int], buf: list[int], tar: list[int]):\n    \"\"\"Solve the Tower of Hanoi problem f(i)\"\"\"\n    # If there is only one disk left in src, move it directly to tar\n    if i == 1:\n        move(src, tar)\n        return\n    # Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf)\n    # Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar)\n    # Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar)\n\ndef solve_hanota(A: list[int], B: list[int], C: list[int]):\n    \"\"\"Solve the Tower of Hanoi problem\"\"\"\n    n = len(A)\n    # Move the top n disks from A to C using B\n    dfs(n, A, B, C)\n
hanota.cpp
/* Move a disk */\nvoid move(vector<int> &src, vector<int> &tar) {\n    // Take out a disk from the top of src\n    int pan = src.back();\n    src.pop_back();\n    // Place the disk on top of tar\n    tar.push_back(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, vector<int> &src, vector<int> &buf, vector<int> &tar) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i == 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(vector<int> &A, vector<int> &B, vector<int> &C) {\n    int n = A.size();\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
hanota.java
/* Move a disk */\nvoid move(List<Integer> src, List<Integer> tar) {\n    // Take out a disk from the top of src\n    Integer pan = src.remove(src.size() - 1);\n    // Place the disk on top of tar\n    tar.add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, List<Integer> src, List<Integer> buf, List<Integer> tar) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i == 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(List<Integer> A, List<Integer> B, List<Integer> C) {\n    int n = A.size();\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
hanota.cs
/* Move a disk */\nvoid Move(List<int> src, List<int> tar) {\n    // Take out a disk from the top of src\n    int pan = src[^1];\n    src.RemoveAt(src.Count - 1);\n    // Place the disk on top of tar\n    tar.Add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid DFS(int i, List<int> src, List<int> buf, List<int> tar) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i == 1) {\n        Move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    DFS(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    Move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    DFS(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid SolveHanota(List<int> A, List<int> B, List<int> C) {\n    int n = A.Count;\n    // Move the top n disks from A to C using B\n    DFS(n, A, B, C);\n}\n
hanota.go
/* Move a disk */\nfunc move(src, tar *list.List) {\n    // Take out a disk from the top of src\n    pan := src.Back()\n    // Place the disk on top of tar\n    tar.PushBack(pan.Value)\n    // Remove top disk from src\n    src.Remove(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunc dfsHanota(i int, src, buf, tar *list.List) {\n    // If there is only one disk left in src, move it directly to tar\n    if i == 1 {\n        move(src, tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfsHanota(i-1, src, tar, buf)\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfsHanota(i-1, buf, src, tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfunc solveHanota(A, B, C *list.List) {\n    n := A.Len()\n    // Move the top n disks from A to C using B\n    dfsHanota(n, A, B, C)\n}\n
hanota.swift
/* Move a disk */\nfunc move(src: inout [Int], tar: inout [Int]) {\n    // Take out a disk from the top of src\n    let pan = src.popLast()!\n    // Place the disk on top of tar\n    tar.append(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunc dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) {\n    // If there is only one disk left in src, move it directly to tar\n    if i == 1 {\n        move(src: &src, tar: &tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i: i - 1, src: &src, buf: &tar, tar: &buf)\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src: &src, tar: &tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i: i - 1, src: &buf, buf: &src, tar: &tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfunc solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) {\n    let n = A.count\n    // The tail of the list is the top of the rod\n    // Move top n disks from src to C using B\n    dfs(i: n, src: &A, buf: &B, tar: &C)\n}\n
hanota.js
/* Move a disk */\nfunction move(src, tar) {\n    // Take out a disk from the top of src\n    const pan = src.pop();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunction dfs(i, src, buf, tar) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i === 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfunction solveHanota(A, B, C) {\n    const n = A.length;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
hanota.ts
/* Move a disk */\nfunction move(src: number[], tar: number[]): void {\n    // Take out a disk from the top of src\n    const pan = src.pop();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunction dfs(i: number, src: number[], buf: number[], tar: number[]): void {\n    // If there is only one disk left in src, move it directly to tar\n    if (i === 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfunction solveHanota(A: number[], B: number[], C: number[]): void {\n    const n = A.length;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
hanota.dart
/* Move a disk */\nvoid move(List<int> src, List<int> tar) {\n  // Take out a disk from the top of src\n  int pan = src.removeLast();\n  // Place the disk on top of tar\n  tar.add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, List<int> src, List<int> buf, List<int> tar) {\n  // If there is only one disk left in src, move it directly to tar\n  if (i == 1) {\n    move(src, tar);\n    return;\n  }\n  // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n  dfs(i - 1, src, tar, buf);\n  // Subproblem f(1): move the remaining disk from src to tar\n  move(src, tar);\n  // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n  dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(List<int> A, List<int> B, List<int> C) {\n  int n = A.length;\n  // Move the top n disks from A to C using B\n  dfs(n, A, B, C);\n}\n
hanota.rs
/* Move a disk */\nfn move_pan(src: &mut Vec<i32>, tar: &mut Vec<i32>) {\n    // Take out a disk from the top of src\n    let pan = src.pop().unwrap();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfn dfs(i: i32, src: &mut Vec<i32>, buf: &mut Vec<i32>, tar: &mut Vec<i32>) {\n    // If there is only one disk left in src, move it directly to tar\n    if i == 1 {\n        move_pan(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move_pan(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfn solve_hanota(A: &mut Vec<i32>, B: &mut Vec<i32>, C: &mut Vec<i32>) {\n    let n = A.len() as i32;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
hanota.c
/* Move a disk */\nvoid move(int *src, int *srcSize, int *tar, int *tarSize) {\n    // Take out a disk from the top of src\n    int pan = src[*srcSize - 1];\n    src[*srcSize - 1] = 0;\n    (*srcSize)--;\n    // Place the disk on top of tar\n    tar[*tarSize] = pan;\n    (*tarSize)++;\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i == 1) {\n        move(src, srcSize, tar, tarSize);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, srcSize, tar, tarSize);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) {\n    // Move the top n disks from A to C using B\n    dfs(*ASize, A, ASize, B, BSize, C, CSize);\n}\n
hanota.kt
/* Move a disk */\nfun move(src: MutableList<Int>, tar: MutableList<Int>) {\n    // Take out a disk from the top of src\n    val pan = src.removeAt(src.size - 1)\n    // Place the disk on top of tar\n    tar.add(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfun dfs(i: Int, src: MutableList<Int>, buf: MutableList<Int>, tar: MutableList<Int>) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i == 1) {\n        move(src, tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf)\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfun solveHanota(A: MutableList<Int>, B: MutableList<Int>, C: MutableList<Int>) {\n    val n = A.size\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C)\n}\n
hanota.rb
### Move one disk ###\ndef move(src, tar)\n  # Take out a disk from the top of src\n  pan = src.pop\n  # Place the disk on top of tar\n  tar << pan\nend\n\n### Solve Tower of Hanoi f(i) ###\ndef dfs(i, src, buf, tar)\n  # If there is only one disk left in src, move it directly to tar\n  if i == 1\n    move(src, tar)\n    return\n  end\n\n  # Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n  dfs(i - 1, src, tar, buf)\n  # Subproblem f(1): move the remaining disk from src to tar\n  move(src, tar)\n  # Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n  dfs(i - 1, buf, src, tar)\nend\n\n### Solve Tower of Hanoi ###\ndef solve_hanota(_A, _B, _C)\n  n = _A.length\n  # Move the top n disks from A to C using B\n  dfs(n, _A, _B, _C)\nend\n

As shown in Figure 12-15, the hanota problem forms a recursion tree of height \\(n\\), where each node represents a subproblem corresponding to an invocation of the dfs() function, therefore the time complexity is \\(O(2^n)\\) and the space complexity is \\(O(n)\\).

Figure 12-15   Recursion tree of the hanota problem

Quote

The hanota problem originates from an ancient legend. In a temple in ancient India, monks had three tall diamond pillars and \\(64\\) golden discs of different sizes. The monks continuously moved the discs, believing that when the last disc was correctly placed, the world would come to an end.

However, even if the monks moved one disc per second, it would take approximately \\(2^{64} \\approx 1.84×10^{19}\\) seconds, which is about \\(5850\\) billion years, far exceeding current estimates of the age of the universe. Therefore, if this legend is true, we should not need to worry about the end of the world.

","path":["Chapter 12. Divide and Conquer","12.4   Hanota Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/summary/","level":1,"title":"12.5   Summary","text":"","path":["Chapter 12. Divide and Conquer","12.5   Summary"],"tags":[]},{"location":"chapter_divide_and_conquer/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Divide and conquer is a common algorithm design strategy, consisting of two phases: divide (partition) and conquer (merge), typically implemented based on recursion.
  • The criteria for determining whether a problem is a divide and conquer problem include: whether the problem can be decomposed, whether subproblems are independent, and whether subproblems can be merged.
  • Merge sort is a typical application of the divide and conquer strategy. It recursively divides an array into two equal-length subarrays until only one element remains, then merges them layer by layer to complete the sorting.
  • Introducing the divide and conquer strategy can often improve algorithm efficiency. On one hand, the divide and conquer strategy reduces the number of operations; on the other hand, it facilitates parallel optimization of the system after division.
  • Divide and conquer can both solve many algorithmic problems and is widely applied in data structure and algorithm design, appearing everywhere.
  • Compared to brute-force search, adaptive search is more efficient. Search algorithms with time complexity of \\(O(\\log n)\\) are typically implemented based on the divide and conquer strategy.
  • Binary search is another typical application of divide and conquer. It does not include the step of merging solutions of subproblems. We can implement binary search through recursive divide and conquer.
  • In the problem of building a binary tree, building the tree (original problem) can be divided into building the left subtree and right subtree (subproblems), which can be achieved by dividing the index intervals of the preorder and inorder traversals.
  • In the hanota problem, a problem of size \\(n\\) can be divided into two subproblems of size \\(n-1\\) and one subproblem of size \\(1\\). After solving these three subproblems in order, the original problem is solved.
","path":["Chapter 12. Divide and Conquer","12.5   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/","level":1,"title":"Chapter 14.   Dynamic Programming","text":"

Abstract

Streams converge into rivers, rivers converge into the sea.

Dynamic programming gathers solutions to small problems into answers to large problems, step by step guiding us to the shore of problem-solving.

","path":["Chapter 14. Dynamic Programming","Chapter 14.   Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 14.1   Introduction to Dynamic Programming
  • 14.2   Characteristics of Dynamic Programming Problems
  • 14.3   Dynamic Programming Problem-Solving Approach
  • 14.4   0-1 Knapsack Problem
  • 14.5   Unbounded Knapsack Problem
  • 14.6   Edit Distance Problem
  • 14.7   Summary
","path":["Chapter 14. Dynamic Programming","Chapter 14.   Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/","level":1,"title":"14.2   Characteristics of Dynamic Programming Problems","text":"

In the previous section, we learned how dynamic programming solves the original problem by decomposing it into subproblems. In fact, subproblem decomposition is a general algorithmic approach, with different emphases in divide and conquer, dynamic programming, and backtracking.

  • Divide and conquer algorithms recursively divide the original problem into multiple independent subproblems until the smallest subproblems are reached, and merge the solutions to the subproblems during backtracking to ultimately obtain the solution to the original problem.
  • Dynamic programming also recursively decomposes problems, but the main difference from divide and conquer algorithms is that subproblems in dynamic programming are interdependent, and many overlapping subproblems appear during the decomposition process.
  • Backtracking algorithms enumerate all possible solutions through trial and error, and avoid unnecessary search branches through pruning. The solution to the original problem consists of a series of decision steps, and we can regard the subsequence before each decision step as a subproblem.

In fact, dynamic programming is commonly used to solve optimization problems, which not only contain overlapping subproblems but also have two other major characteristics: optimal substructure and no aftereffects.

","path":["Chapter 14. Dynamic Programming","14.2   Characteristics of Dynamic Programming Problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/#1421-optimal-substructure","level":2,"title":"14.2.1   Optimal Substructure","text":"

We make a slight modification to the stair climbing problem to make it more suitable for demonstrating the concept of optimal substructure.

Climbing stairs with minimum cost

Given a staircase, where you can climb \\(1\\) or \\(2\\) steps at a time, and each step has a non-negative integer representing the cost you need to pay at that step. Given a non-negative integer array \\(cost\\), where \\(cost[i]\\) represents the cost at the \\(i\\)-th step, and \\(cost[0]\\) is the ground (starting point). What is the minimum cost required to reach the top?

As shown in Figure 14-6, if the costs of the \\(1\\)st, \\(2\\)nd, and \\(3\\)rd steps are \\(1\\), \\(10\\), and \\(1\\) respectively, then climbing from the ground to the \\(3\\)rd step requires a minimum cost of \\(2\\).

Figure 14-6   Minimum cost to climb to the 3rd step

Let \\(dp[i]\\) be the accumulated cost of climbing to the \\(i\\)-th step. Since the \\(i\\)-th step can only come from the \\(i-1\\)-th or \\(i-2\\)-th step, \\(dp[i]\\) can only equal \\(dp[i-1] + cost[i]\\) or \\(dp[i-2] + cost[i]\\). To minimize the cost, we should choose the smaller of the two:

\\[ dp[i] = \\min(dp[i-1], dp[i-2]) + cost[i] \\]

This leads us to the meaning of optimal substructure: the optimal solution to the original problem is constructed from the optimal solutions to the subproblems.

This problem clearly has optimal substructure: we select the better one from the optimal solutions to the two subproblems \\(dp[i-1]\\) and \\(dp[i-2]\\), and use it to construct the optimal solution to the original problem \\(dp[i]\\).

So, does the stair climbing problem from the previous section have optimal substructure? Its goal is to find the number of ways, which seems to be a counting problem, but if we change the question: \"Find the maximum number of ways\". We surprisingly discover that although the problem before and after modification are equivalent, the optimal substructure has emerged: the maximum number of ways for the \\(n\\)-th step equals the sum of the maximum number of ways for the \\(n-1\\)-th and \\(n-2\\)-th steps. Therefore, the interpretation of optimal substructure is quite flexible and will have different meanings in different problems.

According to the state transition equation and the initial states \\(dp[1] = cost[1]\\) and \\(dp[2] = cost[2]\\), we can obtain the dynamic programming code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_cost_climbing_stairs_dp.py
def min_cost_climbing_stairs_dp(cost: list[int]) -> int:\n    \"\"\"Minimum cost climbing stairs: Dynamic programming\"\"\"\n    n = len(cost) - 1\n    if n == 1 or n == 2:\n        return cost[n]\n    # Initialize dp table, used to store solutions to subproblems\n    dp = [0] * (n + 1)\n    # Initial state: preset the solution to the smallest subproblem\n    dp[1], dp[2] = cost[1], cost[2]\n    # State transition: gradually solve larger subproblems from smaller ones\n    for i in range(3, n + 1):\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    return dp[n]\n
min_cost_climbing_stairs_dp.cpp
/* Minimum cost climbing stairs: Dynamic programming */\nint minCostClimbingStairsDP(vector<int> &cost) {\n    int n = cost.size() - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize dp table, used to store solutions to subproblems\n    vector<int> dp(n + 1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
min_cost_climbing_stairs_dp.java
/* Minimum cost climbing stairs: Dynamic programming */\nint minCostClimbingStairsDP(int[] cost) {\n    int n = cost.length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize dp table, used to store solutions to subproblems\n    int[] dp = new int[n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
min_cost_climbing_stairs_dp.cs
/* Minimum cost climbing stairs: Dynamic programming */\nint MinCostClimbingStairsDP(int[] cost) {\n    int n = cost.Length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize dp table, used to store solutions to subproblems\n    int[] dp = new int[n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
min_cost_climbing_stairs_dp.go
/* Minimum cost climbing stairs: Dynamic programming */\nfunc minCostClimbingStairsDP(cost []int) int {\n    n := len(cost) - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    min := func(a, b int) int {\n        if a < b {\n            return a\n        }\n        return b\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    dp := make([]int, n+1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i] = min(dp[i-1], dp[i-2]) + cost[i]\n    }\n    return dp[n]\n}\n
min_cost_climbing_stairs_dp.swift
/* Minimum cost climbing stairs: Dynamic programming */\nfunc minCostClimbingStairsDP(cost: [Int]) -> Int {\n    let n = cost.count - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    var dp = Array(repeating: 0, count: n + 1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3 ... n {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    }\n    return dp[n]\n}\n
min_cost_climbing_stairs_dp.js
/* Minimum cost climbing stairs: Dynamic programming */\nfunction minCostClimbingStairsDP(cost) {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = new Array(n + 1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
min_cost_climbing_stairs_dp.ts
/* Minimum cost climbing stairs: Dynamic programming */\nfunction minCostClimbingStairsDP(cost: Array<number>): number {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = new Array(n + 1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
min_cost_climbing_stairs_dp.dart
/* Minimum cost climbing stairs: Dynamic programming */\nint minCostClimbingStairsDP(List<int> cost) {\n  int n = cost.length - 1;\n  if (n == 1 || n == 2) return cost[n];\n  // Initialize dp table, used to store solutions to subproblems\n  List<int> dp = List.filled(n + 1, 0);\n  // Initial state: preset the solution to the smallest subproblem\n  dp[1] = cost[1];\n  dp[2] = cost[2];\n  // State transition: gradually solve larger subproblems from smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];\n  }\n  return dp[n];\n}\n
min_cost_climbing_stairs_dp.rs
/* Minimum cost climbing stairs: Dynamic programming */\nfn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 {\n    let n = cost.len() - 1;\n    if n == 1 || n == 2 {\n        return cost[n];\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    let mut dp = vec![-1; n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3..=n {\n        dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    dp[n]\n}\n
min_cost_climbing_stairs_dp.c
/* Minimum cost climbing stairs: Dynamic programming */\nint minCostClimbingStairsDP(int cost[], int costSize) {\n    int n = costSize - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize dp table, used to store solutions to subproblems\n    int *dp = calloc(n + 1, sizeof(int));\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    int res = dp[n];\n    // Free memory\n    free(dp);\n    return res;\n}\n
min_cost_climbing_stairs_dp.kt
/* Minimum cost climbing stairs: Dynamic programming */\nfun minCostClimbingStairsDP(cost: IntArray): Int {\n    val n = cost.size - 1\n    if (n == 1 || n == 2) return cost[n]\n    // Initialize dp table, used to store solutions to subproblems\n    val dp = IntArray(n + 1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (i in 3..n) {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    }\n    return dp[n]\n}\n
min_cost_climbing_stairs_dp.rb
### Minimum cost climbing stairs: DP ###\ndef min_cost_climbing_stairs_dp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  # Initialize dp table, used to store solutions to subproblems\n  dp = Array.new(n + 1, 0)\n  # Initial state: preset the solution to the smallest subproblem\n  dp[1], dp[2] = cost[1], cost[2]\n  # State transition: gradually solve larger subproblems from smaller ones\n  (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }\n  dp[n]\nend\n

Figure 14-7 shows the dynamic programming process for the above code.

Figure 14-7   Dynamic programming process for climbing stairs with minimum cost

This problem can also be space-optimized, compressing from one dimension to zero, reducing the space complexity from \\(O(n)\\) to \\(O(1)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_cost_climbing_stairs_dp.py
def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int:\n    \"\"\"Minimum cost climbing stairs: Space-optimized dynamic programming\"\"\"\n    n = len(cost) - 1\n    if n == 1 or n == 2:\n        return cost[n]\n    a, b = cost[1], cost[2]\n    for i in range(3, n + 1):\n        a, b = b, min(a, b) + cost[i]\n    return b\n
min_cost_climbing_stairs_dp.cpp
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(vector<int> &cost) {\n    int n = cost.size() - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.java
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(int[] cost) {\n    int n = cost.length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.cs
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nint MinCostClimbingStairsDPComp(int[] cost) {\n    int n = cost.Length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = Math.Min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.go
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfunc minCostClimbingStairsDPComp(cost []int) int {\n    n := len(cost) - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    min := func(a, b int) int {\n        if a < b {\n            return a\n        }\n        return b\n    }\n    // Initial state: preset the solution to the smallest subproblem\n    a, b := cost[1], cost[2]\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i := 3; i <= n; i++ {\n        tmp := b\n        b = min(a, tmp) + cost[i]\n        a = tmp\n    }\n    return b\n}\n
min_cost_climbing_stairs_dp.swift
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfunc minCostClimbingStairsDPComp(cost: [Int]) -> Int {\n    let n = cost.count - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    var (a, b) = (cost[1], cost[2])\n    for i in 3 ... n {\n        (a, b) = (b, min(a, b) + cost[i])\n    }\n    return b\n}\n
min_cost_climbing_stairs_dp.js
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfunction minCostClimbingStairsDPComp(cost) {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    let a = cost[1],\n        b = cost[2];\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.ts
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfunction minCostClimbingStairsDPComp(cost: Array<number>): number {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    let a = cost[1],\n        b = cost[2];\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.dart
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(List<int> cost) {\n  int n = cost.length - 1;\n  if (n == 1 || n == 2) return cost[n];\n  int a = cost[1], b = cost[2];\n  for (int i = 3; i <= n; i++) {\n    int tmp = b;\n    b = min(a, tmp) + cost[i];\n    a = tmp;\n  }\n  return b;\n}\n
min_cost_climbing_stairs_dp.rs
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 {\n    let n = cost.len() - 1;\n    if n == 1 || n == 2 {\n        return cost[n];\n    };\n    let (mut a, mut b) = (cost[1], cost[2]);\n    for i in 3..=n {\n        let tmp = b;\n        b = cmp::min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    b\n}\n
min_cost_climbing_stairs_dp.c
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(int cost[], int costSize) {\n    int n = costSize - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = myMin(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.kt
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfun minCostClimbingStairsDPComp(cost: IntArray): Int {\n    val n = cost.size - 1\n    if (n == 1 || n == 2) return cost[n]\n    var a = cost[1]\n    var b = cost[2]\n    for (i in 3..n) {\n        val tmp = b\n        b = min(a, tmp) + cost[i]\n        a = tmp\n    }\n    return b\n}\n
min_cost_climbing_stairs_dp.rb
### Minimum cost climbing stairs: DP ###\ndef min_cost_climbing_stairs_dp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  # Initialize dp table, used to store solutions to subproblems\n  dp = Array.new(n + 1, 0)\n  # Initial state: preset the solution to the smallest subproblem\n  dp[1], dp[2] = cost[1], cost[2]\n  # State transition: gradually solve larger subproblems from smaller ones\n  (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }\n  dp[n]\nend\n\n# Minimum cost climbing stairs: Space-optimized dynamic programming\ndef min_cost_climbing_stairs_dp_comp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  a, b = cost[1], cost[2]\n  (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] }\n  b\nend\n
","path":["Chapter 14. Dynamic Programming","14.2   Characteristics of Dynamic Programming Problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/#1422-no-aftereffects","level":2,"title":"14.2.2   No Aftereffects","text":"

No aftereffects is one of the important characteristics that enable dynamic programming to solve problems effectively. Its definition is: given a certain state, its future development is only related to the current state and has nothing to do with all past states.

Taking the stair climbing problem as an example, given state \\(i\\), it will develop into states \\(i+1\\) and \\(i+2\\), corresponding to jumping \\(1\\) step and jumping \\(2\\) steps, respectively. When making these two choices, we do not need to consider the states before state \\(i\\), as they have no effect on the future of state \\(i\\).

However, if we add a constraint to the stair climbing problem, the situation changes.

Climbing stairs with constraint

Given a staircase with \\(n\\) steps, where you can climb \\(1\\) or \\(2\\) steps at a time, but you cannot jump \\(1\\) step in two consecutive rounds. How many ways are there to climb to the top?

As shown in Figure 14-8, there are only \\(2\\) feasible ways to climb to the \\(3\\)rd step. The way of jumping \\(1\\) step three consecutive times does not satisfy the constraint and is therefore discarded.

Figure 14-8   Number of ways to climb to the 3rd step with constraint

In this problem, if the previous round was a jump of \\(1\\) step, then the next round must jump \\(2\\) steps. This means that the next choice cannot be determined solely by the current state (current stair step number), but also depends on the previous state (the stair step number from the previous round).

It is not difficult to see that this problem no longer satisfies no aftereffects, and the state transition equation \\(dp[i] = dp[i-1] + dp[i-2]\\) also fails, because \\(dp[i-1]\\) represents jumping \\(1\\) step in this round, but it includes many solutions where \"the previous round was a jump of \\(1\\) step\", which cannot be directly counted in \\(dp[i]\\) to satisfy the constraint.

For this reason, we need to expand the state definition: state \\([i, j]\\) represents being on the \\(i\\)-th step with the previous round having jumped \\(j\\) steps, where \\(j \\in \\{1, 2\\}\\). This state definition effectively distinguishes whether the previous round was a jump of \\(1\\) step or \\(2\\) steps, allowing us to determine where the current state came from.

  • When the previous round jumped \\(1\\) step, the round before that could only choose to jump \\(2\\) steps, i.e., \\(dp[i, 1]\\) can only be transferred from \\(dp[i-1, 2]\\).
  • When the previous round jumped \\(2\\) steps, the round before that could choose to jump \\(1\\) step or \\(2\\) steps, i.e., \\(dp[i, 2]\\) can be transferred from \\(dp[i-2, 1]\\) or \\(dp[i-2, 2]\\).

As shown in Figure 14-9, under this definition, \\(dp[i, j]\\) represents the number of ways for state \\([i, j]\\). The state transition equation is then:

\\[ \\begin{cases} dp[i, 1] = dp[i-1, 2] \\\\ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] \\end{cases} \\]

Figure 14-9   Recurrence relation considering constraints

Finally, return \\(dp[n, 1] + dp[n, 2]\\), where the sum of the two represents the total number of ways to climb to the \\(n\\)-th step:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_constraint_dp.py
def climbing_stairs_constraint_dp(n: int) -> int:\n    \"\"\"Climbing stairs with constraint: Dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return 1\n    # Initialize dp table, used to store solutions to subproblems\n    dp = [[0] * 3 for _ in range(n + 1)]\n    # Initial state: preset the solution to the smallest subproblem\n    dp[1][1], dp[1][2] = 1, 0\n    dp[2][1], dp[2][2] = 0, 1\n    # State transition: gradually solve larger subproblems from smaller ones\n    for i in range(3, n + 1):\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    return dp[n][1] + dp[n][2]\n
climbing_stairs_constraint_dp.cpp
/* Climbing stairs with constraint: Dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    vector<vector<int>> dp(n + 1, vector<int>(3, 0));\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
climbing_stairs_constraint_dp.java
/* Climbing stairs with constraint: Dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    int[][] dp = new int[n + 1][3];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
climbing_stairs_constraint_dp.cs
/* Climbing stairs with constraint: Dynamic programming */\nint ClimbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    int[,] dp = new int[n + 1, 3];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1, 1] = 1;\n    dp[1, 2] = 0;\n    dp[2, 1] = 0;\n    dp[2, 2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i, 1] = dp[i - 1, 2];\n        dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2];\n    }\n    return dp[n, 1] + dp[n, 2];\n}\n
climbing_stairs_constraint_dp.go
/* Climbing stairs with constraint: Dynamic programming */\nfunc climbingStairsConstraintDP(n int) int {\n    if n == 1 || n == 2 {\n        return 1\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    dp := make([][3]int, n+1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i][1] = dp[i-1][2]\n        dp[i][2] = dp[i-2][1] + dp[i-2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
climbing_stairs_constraint_dp.swift
/* Climbing stairs with constraint: Dynamic programming */\nfunc climbingStairsConstraintDP(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return 1\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3 ... n {\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
climbing_stairs_constraint_dp.js
/* Climbing stairs with constraint: Dynamic programming */\nfunction climbingStairsConstraintDP(n) {\n    if (n === 1 || n === 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = Array.from(new Array(n + 1), () => new Array(3));\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
climbing_stairs_constraint_dp.ts
/* Climbing stairs with constraint: Dynamic programming */\nfunction climbingStairsConstraintDP(n: number): number {\n    if (n === 1 || n === 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = Array.from({ length: n + 1 }, () => new Array(3));\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
climbing_stairs_constraint_dp.dart
/* Climbing stairs with constraint: Dynamic programming */\nint climbingStairsConstraintDP(int n) {\n  if (n == 1 || n == 2) {\n    return 1;\n  }\n  // Initialize dp table, used to store solutions to subproblems\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(3, 0));\n  // Initial state: preset the solution to the smallest subproblem\n  dp[1][1] = 1;\n  dp[1][2] = 0;\n  dp[2][1] = 0;\n  dp[2][2] = 1;\n  // State transition: gradually solve larger subproblems from smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i][1] = dp[i - 1][2];\n    dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n  }\n  return dp[n][1] + dp[n][2];\n}\n
climbing_stairs_constraint_dp.rs
/* Climbing stairs with constraint: Dynamic programming */\nfn climbing_stairs_constraint_dp(n: usize) -> i32 {\n    if n == 1 || n == 2 {\n        return 1;\n    };\n    // Initialize dp table, used to store solutions to subproblems\n    let mut dp = vec![vec![-1; 3]; n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3..=n {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    dp[n][1] + dp[n][2]\n}\n
climbing_stairs_constraint_dp.c
/* Climbing stairs with constraint: Dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(3, sizeof(int));\n    }\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    int res = dp[n][1] + dp[n][2];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
climbing_stairs_constraint_dp.kt
/* Climbing stairs with constraint: Dynamic programming */\nfun climbingStairsConstraintDP(n: Int): Int {\n    if (n == 1 || n == 2) {\n        return 1\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    val dp = Array(n + 1) { IntArray(3) }\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (i in 3..n) {\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
climbing_stairs_constraint_dp.rb
### Climbing stairs with constraint: DP ###\ndef climbing_stairs_constraint_dp(n)\n  return 1 if n == 1 || n == 2\n\n  # Initialize dp table, used to store solutions to subproblems\n  dp = Array.new(n + 1) { Array.new(3, 0) }\n  # Initial state: preset the solution to the smallest subproblem\n  dp[1][1], dp[1][2] = 1, 0\n  dp[2][1], dp[2][2] = 0, 1\n  # State transition: gradually solve larger subproblems from smaller ones\n  for i in 3...(n + 1)\n    dp[i][1] = dp[i - 1][2]\n    dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n  end\n\n  dp[n][1] + dp[n][2]\nend\n

In the above case, since we only need to consider one more preceding state, we can still make the problem satisfy no aftereffects by expanding the state definition. However, some problems have very severe \"aftereffects\".

Climbing stairs with obstacle generation

Given a staircase with \\(n\\) steps, where you can climb \\(1\\) or \\(2\\) steps at a time. It is stipulated that when climbing to the \\(i\\)-th step, the system will automatically place an obstacle on the \\(2i\\)-th step, and thereafter no round is allowed to jump to the \\(2i\\)-th step. For example, if the first two rounds jump to the \\(2\\)nd and \\(3\\)rd steps, then afterwards you cannot jump to the \\(4\\)th and \\(6\\)th steps. How many ways are there to climb to the top?

In this problem, the next jump depends on all past states, because each jump places obstacles on higher steps, affecting future jumps. For such problems, dynamic programming is often difficult to solve.

In fact, many complex combinatorial optimization problems (such as the traveling salesman problem) do not satisfy no aftereffects. For such problems, we usually choose to use other methods, such as heuristic search, genetic algorithms, reinforcement learning, etc., to obtain usable local optimal solutions within a limited time.

","path":["Chapter 14. Dynamic Programming","14.2   Characteristics of Dynamic Programming Problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/","level":1,"title":"14.3   Dynamic Programming Problem-Solving Approach","text":"

The previous two sections introduced the main characteristics of dynamic programming problems. Next, let us explore two more practical issues together.

  1. How to determine whether a problem is a dynamic programming problem?
  2. What is the complete process for solving a dynamic programming problem, and where should we start?
","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1431-problem-determination","level":2,"title":"14.3.1   Problem Determination","text":"

Generally speaking, if a problem contains overlapping subproblems, optimal substructure, and satisfies no aftereffects, then it is usually suitable for solving with dynamic programming. However, it is difficult to directly extract these characteristics from the problem description. Therefore, we usually relax the conditions and first observe whether the problem is suitable for solving with backtracking (exhaustive search).

Problems suitable for solving with backtracking usually satisfy the \"decision tree model\", which means the problem can be described using a tree structure, where each node represents a decision and each path represents a sequence of decisions.

In other words, if a problem contains an explicit concept of decisions, and the solution is generated through a series of decisions, then it satisfies the decision tree model and can usually be solved using backtracking.

On this basis, dynamic programming problems also have some \"bonus points\" for determination.

  • The problem contains descriptions such as maximum (minimum) or most (least), indicating optimization.
  • The problem's state can be represented using a list, multi-dimensional matrix, or tree, and a state has a recurrence relation with its surrounding states.

Correspondingly, there are also some \"penalty points\".

  • The goal of the problem is to find all possible solutions, rather than finding the optimal solution.
  • The problem description has obvious permutation and combination characteristics, requiring the return of specific multiple solutions.

If a problem satisfies the decision tree model and has relatively obvious \"bonus points\", we can assume it is a dynamic programming problem and verify it during the solving process.

","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1432-problem-solving-steps","level":2,"title":"14.3.2   Problem-Solving Steps","text":"

The problem-solving process for dynamic programming varies depending on the nature and difficulty of the problem, but generally follows these steps: describe decisions, define states, establish the \\(dp\\) table, derive state transition equations, determine boundary conditions, etc.

To illustrate the problem-solving steps more vividly, we use a classic problem \"minimum path sum\" as an example.

Question

Given an \\(n \\times m\\) two-dimensional grid grid, where each cell in the grid contains a non-negative integer representing the cost of that cell. A robot starts from the top-left cell and can only move down or right at each step until reaching the bottom-right cell. Return the minimum path sum from the top-left to the bottom-right.

Figure 14-10 shows an example where the minimum path sum for the given grid is \\(13\\).

Figure 14-10   Minimum path sum example data

Step 1: Think about the decisions in each round, define the state, and thus obtain the \\(dp\\) table

The decision in each round of this problem is to move one step down or right from the current cell. Let the row and column indices of the current cell be \\([i, j]\\). After moving down or right, the indices become \\([i+1, j]\\) or \\([i, j+1]\\). Therefore, the state should include two variables, the row index and column index, denoted as \\([i, j]\\).

State \\([i, j]\\) corresponds to the subproblem: the minimum path sum from the starting point \\([0, 0]\\) to \\([i, j]\\), denoted as \\(dp[i, j]\\).

From this, we obtain the two-dimensional \\(dp\\) matrix shown in Figure 14-11, whose size is the same as the input grid \\(grid\\).

Figure 14-11   State definition and dp table

Note

The dynamic programming and backtracking processes can be described as a sequence of decisions, and the state consists of all decision variables. It should contain all variables describing the progress of problem-solving, and should contain sufficient information to derive the next state.

Each state corresponds to a subproblem, and we define a \\(dp\\) table to store the solutions to all subproblems. Each independent variable of the state is a dimension of the \\(dp\\) table. Essentially, the \\(dp\\) table is a mapping between states and solutions to subproblems.

Step 2: Identify the optimal substructure, and then derive the state transition equation

For state \\([i, j]\\), it can only be transferred from the cell above \\([i-1, j]\\) or the cell to the left \\([i, j-1]\\). Therefore, the optimal substructure is: the minimum path sum to reach \\([i, j]\\) is determined by the smaller of the minimum path sums of \\([i, j-1]\\) and \\([i-1, j]\\).

Based on the above analysis, the state transition equation shown in Figure 14-12 can be derived:

\\[ dp[i, j] = \\min(dp[i-1, j], dp[i, j-1]) + grid[i, j] \\]

Figure 14-12   Optimal substructure and state transition equation

Note

Based on the defined \\(dp\\) table, think about the relationship between the original problem and subproblems, and find the method to construct the optimal solution to the original problem from the optimal solutions to the subproblems, which is the optimal substructure.

Once we identify the optimal substructure, we can use it to construct the state transition equation.

Step 3: Determine boundary conditions and state transition order

In this problem, states in the first row can only come from the state to their left, and states in the first column can only come from the state above them. Therefore, the first row \\(i = 0\\) and first column \\(j = 0\\) are boundary conditions.

As shown in Figure 14-13, since each cell is transferred from the cell to its left and the cell above it, we use loops to traverse the matrix, with the outer loop traversing rows and the inner loop traversing columns.

Figure 14-13   Boundary conditions and state transition order

Note

Boundary conditions in dynamic programming are used to initialize the \\(dp\\) table, and in search are used for pruning.

The core of state transition order is to ensure that when computing the solution to the current problem, all the smaller subproblems it depends on have already been computed correctly.

Based on the above analysis, we can directly write the dynamic programming code. However, subproblem decomposition is a top-down approach, so implementing in the order \"brute force search \\(\\rightarrow\\) memoization \\(\\rightarrow\\) dynamic programming\" is more aligned with thinking habits.

","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1-method-1-brute-force-search","level":3,"title":"1.   Method 1: Brute Force Search","text":"

Starting from state \\([i, j]\\), continuously decompose into smaller states \\([i-1, j]\\) and \\([i, j-1]\\). The recursive function includes the following elements.

  • Recursive parameters: state \\([i, j]\\).
  • Return value: minimum path sum from \\([0, 0]\\) to \\([i, j]\\), which is \\(dp[i, j]\\).
  • Termination condition: when \\(i = 0\\) and \\(j = 0\\), return cost \\(grid[0, 0]\\).
  • Pruning: when \\(i < 0\\) or \\(j < 0\\), the index is out of bounds, return cost \\(+\\infty\\), representing infeasibility.

The implementation code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int:\n    \"\"\"Minimum path sum: Brute-force search\"\"\"\n    # If it's the top-left cell, terminate the search\n    if i == 0 and j == 0:\n        return grid[0][0]\n    # If row or column index is out of bounds, return +∞ cost\n    if i < 0 or j < 0:\n        return inf\n    # Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    up = min_path_sum_dfs(grid, i - 1, j)\n    left = min_path_sum_dfs(grid, i, j - 1)\n    # Return the minimum path cost from top-left to (i, j)\n    return min(left, up) + grid[i][j]\n
min_path_sum.cpp
/* Minimum path sum: Brute-force search */\nint minPathSumDFS(vector<vector<int>> &grid, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;\n}\n
min_path_sum.java
/* Minimum path sum: Brute-force search */\nint minPathSumDFS(int[][] grid, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Integer.MAX_VALUE;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
min_path_sum.cs
/* Minimum path sum: Brute-force search */\nint MinPathSumDFS(int[][] grid, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return int.MaxValue;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    int up = MinPathSumDFS(grid, i - 1, j);\n    int left = MinPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return Math.Min(left, up) + grid[i][j];\n}\n
min_path_sum.go
/* Minimum path sum: Brute-force search */\nfunc minPathSumDFS(grid [][]int, i, j int) int {\n    // If it's the top-left cell, terminate the search\n    if i == 0 && j == 0 {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return math.MaxInt\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    up := minPathSumDFS(grid, i-1, j)\n    left := minPathSumDFS(grid, i, j-1)\n    // Return the minimum path cost from top-left to (i, j)\n    return int(math.Min(float64(left), float64(up))) + grid[i][j]\n}\n
min_path_sum.swift
/* Minimum path sum: Brute-force search */\nfunc minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int {\n    // If it's the top-left cell, terminate the search\n    if i == 0, j == 0 {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return .max\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    let up = minPathSumDFS(grid: grid, i: i - 1, j: j)\n    let left = minPathSumDFS(grid: grid, i: i, j: j - 1)\n    // Return the minimum path cost from top-left to (i, j)\n    return min(left, up) + grid[i][j]\n}\n
min_path_sum.js
/* Minimum path sum: Brute-force search */\nfunction minPathSumDFS(grid, i, j) {\n    // If it's the top-left cell, terminate the search\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    const up = minPathSumDFS(grid, i - 1, j);\n    const left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
min_path_sum.ts
/* Minimum path sum: Brute-force search */\nfunction minPathSumDFS(\n    grid: Array<Array<number>>,\n    i: number,\n    j: number\n): number {\n    // If it's the top-left cell, terminate the search\n    if (i === 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    const up = minPathSumDFS(grid, i - 1, j);\n    const left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
min_path_sum.dart
/* Minimum path sum: Brute-force search */\nint minPathSumDFS(List<List<int>> grid, int i, int j) {\n  // If it's the top-left cell, terminate the search\n  if (i == 0 && j == 0) {\n    return grid[0][0];\n  }\n  // If row or column index is out of bounds, return +∞ cost\n  if (i < 0 || j < 0) {\n    // In Dart, int type is fixed-range integer, no value representing \"infinity\"\n    return BigInt.from(2).pow(31).toInt();\n  }\n  // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n  int up = minPathSumDFS(grid, i - 1, j);\n  int left = minPathSumDFS(grid, i, j - 1);\n  // Return the minimum path cost from top-left to (i, j)\n  return min(left, up) + grid[i][j];\n}\n
min_path_sum.rs
/* Minimum path sum: Brute-force search */\nfn min_path_sum_dfs(grid: &Vec<Vec<i32>>, i: i32, j: i32) -> i32 {\n    // If it's the top-left cell, terminate the search\n    if i == 0 && j == 0 {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return i32::MAX;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    let up = min_path_sum_dfs(grid, i - 1, j);\n    let left = min_path_sum_dfs(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    std::cmp::min(left, up) + grid[i as usize][j as usize]\n}\n
min_path_sum.c
/* Minimum path sum: Brute-force search */\nint minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;\n}\n
min_path_sum.kt
/* Minimum path sum: Brute-force search */\nfun minPathSumDFS(grid: Array<IntArray>, i: Int, j: Int): Int {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Int.MAX_VALUE\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    val up = minPathSumDFS(grid, i - 1, j)\n    val left = minPathSumDFS(grid, i, j - 1)\n    // Return the minimum path cost from top-left to (i, j)\n    return min(left, up) + grid[i][j]\n}\n
min_path_sum.rb
### Minimum path sum: brute force search ###\ndef min_path_sum_dfs(grid, i, j)\n  # If it's the top-left cell, terminate the search\n  return grid[i][j] if i == 0 && j == 0\n  # If row or column index is out of bounds, return +∞ cost\n  return Float::INFINITY if i < 0 || j < 0\n  # Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n  up = min_path_sum_dfs(grid, i - 1, j)\n  left = min_path_sum_dfs(grid, i, j - 1)\n  # Return the minimum path cost from top-left to (i, j)\n  [left, up].min + grid[i][j]\nend\n

Figure 14-14 shows the recursion tree rooted at \\(dp[2, 1]\\), which includes some overlapping subproblems whose number will increase sharply as the size of grid grid grows.

Essentially, the reason for overlapping subproblems is: there are multiple paths from the top-left corner to reach a certain cell.

Figure 14-14   Brute force search recursion tree

Each state has two choices, down and right, so the total number of steps from the top-left corner to the bottom-right corner is \\(m + n - 2\\), giving a worst-case time complexity of \\(O(2^{m + n})\\), where \\(n\\) and \\(m\\) are the number of rows and columns of the grid, respectively. Note that this calculation does not account for situations near the grid boundaries, where only one choice remains when reaching the grid boundary, so the actual number of paths will be somewhat less.

","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#2-method-2-memoization","level":3,"title":"2.   Method 2: Memoization","text":"

We introduce a memo list mem of the same size as grid grid to record the solutions to subproblems and prune overlapping subproblems:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
def min_path_sum_dfs_mem(\n    grid: list[list[int]], mem: list[list[int]], i: int, j: int\n) -> int:\n    \"\"\"Minimum path sum: Memoization search\"\"\"\n    # If it's the top-left cell, terminate the search\n    if i == 0 and j == 0:\n        return grid[0][0]\n    # If row or column index is out of bounds, return +∞ cost\n    if i < 0 or j < 0:\n        return inf\n    # If there's a record, return it directly\n    if mem[i][j] != -1:\n        return mem[i][j]\n    # Minimum path cost for left and upper cells\n    up = min_path_sum_dfs_mem(grid, mem, i - 1, j)\n    left = min_path_sum_dfs_mem(grid, mem, i, j - 1)\n    # Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n
min_path_sum.cpp
/* Minimum path sum: Memoization search */\nint minPathSumDFSMem(vector<vector<int>> &grid, vector<vector<int>> &mem, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;\n    return mem[i][j];\n}\n
min_path_sum.java
/* Minimum path sum: Memoization search */\nint minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Integer.MAX_VALUE;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
min_path_sum.cs
/* Minimum path sum: Memoization search */\nint MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return int.MaxValue;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    int up = MinPathSumDFSMem(grid, mem, i - 1, j);\n    int left = MinPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = Math.Min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
min_path_sum.go
/* Minimum path sum: Memoization search */\nfunc minPathSumDFSMem(grid, mem [][]int, i, j int) int {\n    // If it's the top-left cell, terminate the search\n    if i == 0 && j == 0 {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return math.MaxInt\n    }\n    // If there's a record, return it directly\n    if mem[i][j] != -1 {\n        return mem[i][j]\n    }\n    // Minimum path cost for left and upper cells\n    up := minPathSumDFSMem(grid, mem, i-1, j)\n    left := minPathSumDFSMem(grid, mem, i, j-1)\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j]\n    return mem[i][j]\n}\n
min_path_sum.swift
/* Minimum path sum: Memoization search */\nfunc minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int {\n    // If it's the top-left cell, terminate the search\n    if i == 0, j == 0 {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return .max\n    }\n    // If there's a record, return it directly\n    if mem[i][j] != -1 {\n        return mem[i][j]\n    }\n    // Minimum path cost for left and upper cells\n    let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j)\n    let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1)\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n}\n
min_path_sum.js
/* Minimum path sum: Memoization search */\nfunction minPathSumDFSMem(grid, mem, i, j) {\n    // If it's the top-left cell, terminate the search\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] !== -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    const up = minPathSumDFSMem(grid, mem, i - 1, j);\n    const left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
min_path_sum.ts
/* Minimum path sum: Memoization search */\nfunction minPathSumDFSMem(\n    grid: Array<Array<number>>,\n    mem: Array<Array<number>>,\n    i: number,\n    j: number\n): number {\n    // If it's the top-left cell, terminate the search\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    const up = minPathSumDFSMem(grid, mem, i - 1, j);\n    const left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
min_path_sum.dart
/* Minimum path sum: Memoization search */\nint minPathSumDFSMem(List<List<int>> grid, List<List<int>> mem, int i, int j) {\n  // If it's the top-left cell, terminate the search\n  if (i == 0 && j == 0) {\n    return grid[0][0];\n  }\n  // If row or column index is out of bounds, return +∞ cost\n  if (i < 0 || j < 0) {\n    // In Dart, int type is fixed-range integer, no value representing \"infinity\"\n    return BigInt.from(2).pow(31).toInt();\n  }\n  // If there's a record, return it directly\n  if (mem[i][j] != -1) {\n    return mem[i][j];\n  }\n  // Minimum path cost for left and upper cells\n  int up = minPathSumDFSMem(grid, mem, i - 1, j);\n  int left = minPathSumDFSMem(grid, mem, i, j - 1);\n  // Record and return the minimum path cost from top-left to (i, j)\n  mem[i][j] = min(left, up) + grid[i][j];\n  return mem[i][j];\n}\n
min_path_sum.rs
/* Minimum path sum: Memoization search */\nfn min_path_sum_dfs_mem(grid: &Vec<Vec<i32>>, mem: &mut Vec<Vec<i32>>, i: i32, j: i32) -> i32 {\n    // If it's the top-left cell, terminate the search\n    if i == 0 && j == 0 {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return i32::MAX;\n    }\n    // If there's a record, return it directly\n    if mem[i as usize][j as usize] != -1 {\n        return mem[i as usize][j as usize];\n    }\n    // Minimum path cost for left and upper cells\n    let up = min_path_sum_dfs_mem(grid, mem, i - 1, j);\n    let left = min_path_sum_dfs_mem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize];\n    mem[i as usize][j as usize]\n}\n
min_path_sum.c
/* Minimum path sum: Memoization search */\nint minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;\n    return mem[i][j];\n}\n
min_path_sum.kt
/* Minimum path sum: Memoization search */\nfun minPathSumDFSMem(\n    grid: Array<IntArray>,\n    mem: Array<IntArray>,\n    i: Int,\n    j: Int\n): Int {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Int.MAX_VALUE\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j]\n    }\n    // Minimum path cost for left and upper cells\n    val up = minPathSumDFSMem(grid, mem, i - 1, j)\n    val left = minPathSumDFSMem(grid, mem, i, j - 1)\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n}\n
min_path_sum.rb
### Minimum path sum: memoization search ###\ndef min_path_sum_dfs_mem(grid, mem, i, j)\n  # If it's the top-left cell, terminate the search\n  return grid[0][0] if i == 0 && j == 0\n  # If row or column index is out of bounds, return +∞ cost\n  return Float::INFINITY if i < 0 || j < 0\n  # If there's a record, return it directly\n  return mem[i][j] if mem[i][j] != -1\n  # Minimum path cost for left and upper cells\n  up = min_path_sum_dfs_mem(grid, mem, i - 1, j)\n  left = min_path_sum_dfs_mem(grid, mem, i, j - 1)\n  # Record and return the minimum path cost from top-left to (i, j)\n  mem[i][j] = [left, up].min + grid[i][j]\nend\n

As shown in Figure 14-15, after introducing memoization, all subproblem solutions only need to be computed once, so the time complexity depends on the total number of states, which is the grid size \\(O(nm)\\).

Figure 14-15   Memoization recursion tree

","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#3-method-3-dynamic-programming","level":3,"title":"3.   Method 3: Dynamic Programming","text":"

Implement the dynamic programming solution based on iteration, as shown in the code below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
def min_path_sum_dp(grid: list[list[int]]) -> int:\n    \"\"\"Minimum path sum: Dynamic programming\"\"\"\n    n, m = len(grid), len(grid[0])\n    # Initialize dp table\n    dp = [[0] * m for _ in range(n)]\n    dp[0][0] = grid[0][0]\n    # State transition: first row\n    for j in range(1, m):\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    # State transition: first column\n    for i in range(1, n):\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    # State transition: rest of the rows and columns\n    for i in range(1, n):\n        for j in range(1, m):\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n    return dp[n - 1][m - 1]\n
min_path_sum.cpp
/* Minimum path sum: Dynamic programming */\nint minPathSumDP(vector<vector<int>> &grid) {\n    int n = grid.size(), m = grid[0].size();\n    // Initialize dp table\n    vector<vector<int>> dp(n, vector<int>(m));\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
min_path_sum.java
/* Minimum path sum: Dynamic programming */\nint minPathSumDP(int[][] grid) {\n    int n = grid.length, m = grid[0].length;\n    // Initialize dp table\n    int[][] dp = new int[n][m];\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
min_path_sum.cs
/* Minimum path sum: Dynamic programming */\nint MinPathSumDP(int[][] grid) {\n    int n = grid.Length, m = grid[0].Length;\n    // Initialize dp table\n    int[,] dp = new int[n, m];\n    dp[0, 0] = grid[0][0];\n    // State transition: first row\n    for (int j = 1; j < m; j++) {\n        dp[0, j] = dp[0, j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (int i = 1; i < n; i++) {\n        dp[i, 0] = dp[i - 1, 0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1, m - 1];\n}\n
min_path_sum.go
/* Minimum path sum: Dynamic programming */\nfunc minPathSumDP(grid [][]int) int {\n    n, m := len(grid), len(grid[0])\n    // Initialize dp table\n    dp := make([][]int, n)\n    for i := 0; i < n; i++ {\n        dp[i] = make([]int, m)\n    }\n    dp[0][0] = grid[0][0]\n    // State transition: first row\n    for j := 1; j < m; j++ {\n        dp[0][j] = dp[0][j-1] + grid[0][j]\n    }\n    // State transition: first column\n    for i := 1; i < n; i++ {\n        dp[i][0] = dp[i-1][0] + grid[i][0]\n    }\n    // State transition: rest of the rows and columns\n    for i := 1; i < n; i++ {\n        for j := 1; j < m; j++ {\n            dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j]\n        }\n    }\n    return dp[n-1][m-1]\n}\n
min_path_sum.swift
/* Minimum path sum: Dynamic programming */\nfunc minPathSumDP(grid: [[Int]]) -> Int {\n    let n = grid.count\n    let m = grid[0].count\n    // Initialize dp table\n    var dp = Array(repeating: Array(repeating: 0, count: m), count: n)\n    dp[0][0] = grid[0][0]\n    // State transition: first row\n    for j in 1 ..< m {\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    }\n    // State transition: first column\n    for i in 1 ..< n {\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    }\n    // State transition: rest of the rows and columns\n    for i in 1 ..< n {\n        for j in 1 ..< m {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n        }\n    }\n    return dp[n - 1][m - 1]\n}\n
min_path_sum.js
/* Minimum path sum: Dynamic programming */\nfunction minPathSumDP(grid) {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize dp table\n    const dp = Array.from({ length: n }, () =>\n        Array.from({ length: m }, () => 0)\n    );\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for (let j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (let i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i < n; i++) {\n        for (let j = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
min_path_sum.ts
/* Minimum path sum: Dynamic programming */\nfunction minPathSumDP(grid: Array<Array<number>>): number {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize dp table\n    const dp = Array.from({ length: n }, () =>\n        Array.from({ length: m }, () => 0)\n    );\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for (let j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (let i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i < n; i++) {\n        for (let j: number = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
min_path_sum.dart
/* Minimum path sum: Dynamic programming */\nint minPathSumDP(List<List<int>> grid) {\n  int n = grid.length, m = grid[0].length;\n  // Initialize dp table\n  List<List<int>> dp = List.generate(n, (i) => List.filled(m, 0));\n  dp[0][0] = grid[0][0];\n  // State transition: first row\n  for (int j = 1; j < m; j++) {\n    dp[0][j] = dp[0][j - 1] + grid[0][j];\n  }\n  // State transition: first column\n  for (int i = 1; i < n; i++) {\n    dp[i][0] = dp[i - 1][0] + grid[i][0];\n  }\n  // State transition: rest of the rows and columns\n  for (int i = 1; i < n; i++) {\n    for (int j = 1; j < m; j++) {\n      dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n    }\n  }\n  return dp[n - 1][m - 1];\n}\n
min_path_sum.rs
/* Minimum path sum: Dynamic programming */\nfn min_path_sum_dp(grid: &Vec<Vec<i32>>) -> i32 {\n    let (n, m) = (grid.len(), grid[0].len());\n    // Initialize dp table\n    let mut dp = vec![vec![0; m]; n];\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for j in 1..m {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for i in 1..n {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for i in 1..n {\n        for j in 1..m {\n            dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    dp[n - 1][m - 1]\n}\n
min_path_sum.c
/* Minimum path sum: Dynamic programming */\nint minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {\n    // Initialize dp table\n    int **dp = malloc(n * sizeof(int *));\n    for (int i = 0; i < n; i++) {\n        dp[i] = calloc(m, sizeof(int));\n    }\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    int res = dp[n - 1][m - 1];\n    // Free memory\n    for (int i = 0; i < n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
min_path_sum.kt
/* Minimum path sum: Dynamic programming */\nfun minPathSumDP(grid: Array<IntArray>): Int {\n    val n = grid.size\n    val m = grid[0].size\n    // Initialize dp table\n    val dp = Array(n) { IntArray(m) }\n    dp[0][0] = grid[0][0]\n    // State transition: first row\n    for (j in 1..<m) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    }\n    // State transition: first column\n    for (i in 1..<n) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    }\n    // State transition: rest of the rows and columns\n    for (i in 1..<n) {\n        for (j in 1..<m) {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n        }\n    }\n    return dp[n - 1][m - 1]\n}\n
min_path_sum.rb
### Minimum path sum: dynamic programming ###\ndef min_path_sum_dp(grid)\n  n, m = grid.length, grid.first.length\n  # Initialize dp table\n  dp = Array.new(n) { Array.new(m, 0) }\n  dp[0][0] = grid[0][0]\n  # State transition: first row\n  (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] }\n  # State transition: first column\n  (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] }\n  # State transition: rest of the rows and columns\n  for i in 1...n\n    for j in 1...m\n      dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j]\n    end\n  end\n  dp[n -1][m -1]\nend\n

Figure 14-16 shows the state transition process for minimum path sum, which traverses the entire grid, thus the time complexity is \\(O(nm)\\).

The array dp has size \\(n \\times m\\), thus the space complexity is \\(O(nm)\\).

<1><2><3><4><5><6><7><8><9><10><11><12>

Figure 14-16   Dynamic programming process for minimum path sum

","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#4-space-optimization","level":3,"title":"4.   Space Optimization","text":"

Since each cell is only related to the cell to its left and the cell above it, we can use a single-row array to implement the \\(dp\\) table.

Note that since the array dp can only represent the state of one row, we cannot initialize the first column state in advance, but rather update it when traversing each row:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
def min_path_sum_dp_comp(grid: list[list[int]]) -> int:\n    \"\"\"Minimum path sum: Space-optimized dynamic programming\"\"\"\n    n, m = len(grid), len(grid[0])\n    # Initialize dp table\n    dp = [0] * m\n    # State transition: first row\n    dp[0] = grid[0][0]\n    for j in range(1, m):\n        dp[j] = dp[j - 1] + grid[0][j]\n    # State transition: rest of the rows\n    for i in range(1, n):\n        # State transition: first column\n        dp[0] = dp[0] + grid[i][0]\n        # State transition: rest of the columns\n        for j in range(1, m):\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n    return dp[m - 1]\n
min_path_sum.cpp
/* Minimum path sum: Space-optimized dynamic programming */\nint minPathSumDPComp(vector<vector<int>> &grid) {\n    int n = grid.size(), m = grid[0].size();\n    // Initialize dp table\n    vector<int> dp(m);\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
min_path_sum.java
/* Minimum path sum: Space-optimized dynamic programming */\nint minPathSumDPComp(int[][] grid) {\n    int n = grid.length, m = grid[0].length;\n    // Initialize dp table\n    int[] dp = new int[m];\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
min_path_sum.cs
/* Minimum path sum: Space-optimized dynamic programming */\nint MinPathSumDPComp(int[][] grid) {\n    int n = grid.Length, m = grid[0].Length;\n    // Initialize dp table\n    int[] dp = new int[m];\n    dp[0] = grid[0][0];\n    // State transition: first row\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
min_path_sum.go
/* Minimum path sum: Space-optimized dynamic programming */\nfunc minPathSumDPComp(grid [][]int) int {\n    n, m := len(grid), len(grid[0])\n    // Initialize dp table\n    dp := make([]int, m)\n    // State transition: first row\n    dp[0] = grid[0][0]\n    for j := 1; j < m; j++ {\n        dp[j] = dp[j-1] + grid[0][j]\n    }\n    // State transition: rest of the rows and columns\n    for i := 1; i < n; i++ {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transition: rest of the columns\n        for j := 1; j < m; j++ {\n            dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j]\n        }\n    }\n    return dp[m-1]\n}\n
min_path_sum.swift
/* Minimum path sum: Space-optimized dynamic programming */\nfunc minPathSumDPComp(grid: [[Int]]) -> Int {\n    let n = grid.count\n    let m = grid[0].count\n    // Initialize dp table\n    var dp = Array(repeating: 0, count: m)\n    // State transition: first row\n    dp[0] = grid[0][0]\n    for j in 1 ..< m {\n        dp[j] = dp[j - 1] + grid[0][j]\n    }\n    // State transition: rest of the rows\n    for i in 1 ..< n {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transition: rest of the columns\n        for j in 1 ..< m {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n        }\n    }\n    return dp[m - 1]\n}\n
min_path_sum.js
/* Minimum path sum: Space-optimized dynamic programming */\nfunction minPathSumDPComp(grid) {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize dp table\n    const dp = new Array(m);\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for (let j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (let i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (let j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
min_path_sum.ts
/* Minimum path sum: Space-optimized dynamic programming */\nfunction minPathSumDPComp(grid: Array<Array<number>>): number {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize dp table\n    const dp = new Array(m);\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for (let j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (let i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (let j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
min_path_sum.dart
/* Minimum path sum: Space-optimized dynamic programming */\nint minPathSumDPComp(List<List<int>> grid) {\n  int n = grid.length, m = grid[0].length;\n  // Initialize dp table\n  List<int> dp = List.filled(m, 0);\n  dp[0] = grid[0][0];\n  for (int j = 1; j < m; j++) {\n    dp[j] = dp[j - 1] + grid[0][j];\n  }\n  // State transition: rest of the rows\n  for (int i = 1; i < n; i++) {\n    // State transition: first column\n    dp[0] = dp[0] + grid[i][0];\n    // State transition: rest of the columns\n    for (int j = 1; j < m; j++) {\n      dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];\n    }\n  }\n  return dp[m - 1];\n}\n
min_path_sum.rs
/* Minimum path sum: Space-optimized dynamic programming */\nfn min_path_sum_dp_comp(grid: &Vec<Vec<i32>>) -> i32 {\n    let (n, m) = (grid.len(), grid[0].len());\n    // Initialize dp table\n    let mut dp = vec![0; m];\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for j in 1..m {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for i in 1..n {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for j in 1..m {\n            dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    dp[m - 1]\n}\n
min_path_sum.c
/* Minimum path sum: Space-optimized dynamic programming */\nint minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {\n    // Initialize dp table\n    int *dp = calloc(m, sizeof(int));\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    int res = dp[m - 1];\n    // Free memory\n    free(dp);\n    return res;\n}\n
min_path_sum.kt
/* Minimum path sum: Space-optimized dynamic programming */\nfun minPathSumDPComp(grid: Array<IntArray>): Int {\n    val n = grid.size\n    val m = grid[0].size\n    // Initialize dp table\n    val dp = IntArray(m)\n    // State transition: first row\n    dp[0] = grid[0][0]\n    for (j in 1..<m) {\n        dp[j] = dp[j - 1] + grid[0][j]\n    }\n    // State transition: rest of the rows\n    for (i in 1..<n) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transition: rest of the columns\n        for (j in 1..<m) {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n        }\n    }\n    return dp[m - 1]\n}\n
min_path_sum.rb
### Minimum path sum: space-optimized DP ###\ndef min_path_sum_dp_comp(grid)\n  n, m = grid.length, grid.first.length\n  # Initialize dp table\n  dp = Array.new(m, 0)\n  # State transition: first row\n  dp[0] = grid[0][0]\n  (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] }\n  # State transition: rest of the rows\n  for i in 1...n\n    # State transition: first column\n    dp[0] = dp[0] + grid[i][0]\n    # State transition: rest of the columns\n    (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] }\n  end\n  dp[m - 1]\nend\n
","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/","level":1,"title":"14.6   Edit Distance Problem","text":"

Edit distance, also known as Levenshtein distance, refers to the minimum number of edits required to transform one string into another, commonly used in information retrieval and natural language processing to measure the similarity between two sequences.

Question

Given two strings \\(s\\) and \\(t\\), return the minimum number of edits required to transform \\(s\\) into \\(t\\).

You can perform three types of edit operations on a string: insert a character, delete a character, or replace a character with any other character.

As shown in Figure 14-27, transforming kitten into sitting requires 3 edits, including 2 replacements and 1 insertion; transforming hello into algo requires 3 steps, including 2 replacements and 1 deletion.

Figure 14-27   Example data for edit distance

The edit distance problem can be naturally explained using the decision tree model. Strings correspond to tree nodes, and a round of decision (one edit operation) corresponds to an edge of the tree.

As shown in Figure 14-28, without restricting operations, each node can branch into many edges, with each edge corresponding to one operation, meaning there are many possible paths to transform hello into algo.

From the perspective of the decision tree, the goal of this problem is to find the shortest path between node hello and node algo.

Figure 14-28   Representing edit distance problem based on decision tree model

","path":["Chapter 14. Dynamic Programming","14.6   Edit Distance Problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#1-dynamic-programming-approach","level":3,"title":"1.   Dynamic Programming Approach","text":"

Step 1: Think about the decisions in each round, define the state, and thus obtain the \\(dp\\) table

Each round of decision involves performing one edit operation on string \\(s\\).

We want the problem scale to gradually decrease during the editing process, which allows us to construct subproblems. Let the lengths of strings \\(s\\) and \\(t\\) be \\(n\\) and \\(m\\) respectively. We first consider the tail characters of the two strings, \\(s[n-1]\\) and \\(t[m-1]\\).

  • If \\(s[n-1]\\) and \\(t[m-1]\\) are the same, we can skip them and directly consider \\(s[n-2]\\) and \\(t[m-2]\\).
  • If \\(s[n-1]\\) and \\(t[m-1]\\) are different, we need to perform one edit on \\(s\\) (insert, delete, or replace) to make the tail characters of the two strings the same, allowing us to skip them and consider a smaller-scale problem.

In other words, each round of decision (edit operation) we make on string \\(s\\) will change the remaining characters to be matched in \\(s\\) and \\(t\\). Therefore, the state is the \\(i\\)-th and \\(j\\)-th characters currently being considered in \\(s\\) and \\(t\\), denoted as \\([i, j]\\).

State \\([i, j]\\) corresponds to the subproblem: the minimum number of edits required to change the first \\(i\\) characters of \\(s\\) into the first \\(j\\) characters of \\(t\\).

From this, we obtain a two-dimensional \\(dp\\) table of size \\((i+1) \\times (j+1)\\).

Step 2: Identify the optimal substructure, and then derive the state transition equation

Consider subproblem \\(dp[i, j]\\), where the tail characters of the corresponding two strings are \\(s[i-1]\\) and \\(t[j-1]\\), which can be divided into the three cases shown in Figure 14-29 based on different edit operations.

  1. Insert \\(t[j-1]\\) after \\(s[i-1]\\), then the remaining subproblem is \\(dp[i, j-1]\\).
  2. Delete \\(s[i-1]\\), then the remaining subproblem is \\(dp[i-1, j]\\).
  3. Replace \\(s[i-1]\\) with \\(t[j-1]\\), then the remaining subproblem is \\(dp[i-1, j-1]\\).

Figure 14-29   State transition for edit distance

Based on the above analysis, the optimal substructure can be obtained: the minimum number of edits for \\(dp[i, j]\\) equals the minimum among the minimum edit steps of \\(dp[i, j-1]\\), \\(dp[i-1, j]\\), and \\(dp[i-1, j-1]\\), plus the edit step \\(1\\) for this time. The corresponding state transition equation is:

\\[ dp[i, j] = \\min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 \\]

Please note that when \\(s[i-1]\\) and \\(t[j-1]\\) are the same, no edit is required for the current character, in which case the state transition equation is:

\\[ dp[i, j] = dp[i-1, j-1] \\]

Step 3: Determine boundary conditions and state transition order

When both strings are empty, the number of edit steps is \\(0\\), i.e., \\(dp[0, 0] = 0\\). When \\(s\\) is empty but \\(t\\) is not, the minimum number of edit steps equals the length of \\(t\\), i.e., the first row \\(dp[0, j] = j\\). When \\(s\\) is not empty but \\(t\\) is empty, the minimum number of edit steps equals the length of \\(s\\), i.e., the first column \\(dp[i, 0] = i\\).

Observing the state transition equation, the solution \\(dp[i, j]\\) depends on solutions to the left, above, and upper-left, so the entire \\(dp\\) table can be traversed in order through two nested loops.

","path":["Chapter 14. Dynamic Programming","14.6   Edit Distance Problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby edit_distance.py
def edit_distance_dp(s: str, t: str) -> int:\n    \"\"\"Edit distance: Dynamic programming\"\"\"\n    n, m = len(s), len(t)\n    dp = [[0] * (m + 1) for _ in range(n + 1)]\n    # State transition: first row and first column\n    for i in range(1, n + 1):\n        dp[i][0] = i\n    for j in range(1, m + 1):\n        dp[0][j] = j\n    # State transition: rest of the rows and columns\n    for i in range(1, n + 1):\n        for j in range(1, m + 1):\n            if s[i - 1] == t[j - 1]:\n                # If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1]\n            else:\n                # Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1\n    return dp[n][m]\n
edit_distance.cpp
/* Edit distance: Dynamic programming */\nint editDistanceDP(string s, string t) {\n    int n = s.length(), m = t.length();\n    vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));\n    // State transition: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
edit_distance.java
/* Edit distance: Dynamic programming */\nint editDistanceDP(String s, String t) {\n    int n = s.length(), m = t.length();\n    int[][] dp = new int[n + 1][m + 1];\n    // State transition: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) == t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
edit_distance.cs
/* Edit distance: Dynamic programming */\nint EditDistanceDP(string s, string t) {\n    int n = s.Length, m = t.Length;\n    int[,] dp = new int[n + 1, m + 1];\n    // State transition: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i, 0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0, j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[i, j] = dp[i - 1, j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n, m];\n}\n
edit_distance.go
/* Edit distance: Dynamic programming */\nfunc editDistanceDP(s string, t string) int {\n    n := len(s)\n    m := len(t)\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, m+1)\n    }\n    // State transition: first row and first column\n    for i := 1; i <= n; i++ {\n        dp[i][0] = i\n    }\n    for j := 1; j <= m; j++ {\n        dp[0][j] = j\n    }\n    // State transition: rest of the rows and columns\n    for i := 1; i <= n; i++ {\n        for j := 1; j <= m; j++ {\n            if s[i-1] == t[j-1] {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i-1][j-1]\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
edit_distance.swift
/* Edit distance: Dynamic programming */\nfunc editDistanceDP(s: String, t: String) -> Int {\n    let n = s.utf8CString.count\n    let m = t.utf8CString.count\n    var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1)\n    // State transition: first row and first column\n    for i in 1 ... n {\n        dp[i][0] = i\n    }\n    for j in 1 ... m {\n        dp[0][j] = j\n    }\n    // State transition: rest of the rows and columns\n    for i in 1 ... n {\n        for j in 1 ... m {\n            if s.utf8CString[i - 1] == t.utf8CString[j - 1] {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1]\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
edit_distance.js
/* Edit distance: Dynamic programming */\nfunction editDistanceDP(s, t) {\n    const n = s.length,\n        m = t.length;\n    const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));\n    // State transition: first row and first column\n    for (let i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (let j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] =\n                    Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
edit_distance.ts
/* Edit distance: Dynamic programming */\nfunction editDistanceDP(s: string, t: string): number {\n    const n = s.length,\n        m = t.length;\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: m + 1 }, () => 0)\n    );\n    // State transition: first row and first column\n    for (let i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (let j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] =\n                    Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
edit_distance.dart
/* Edit distance: Dynamic programming */\nint editDistanceDP(String s, String t) {\n  int n = s.length, m = t.length;\n  List<List<int>> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0));\n  // State transition: first row and first column\n  for (int i = 1; i <= n; i++) {\n    dp[i][0] = i;\n  }\n  for (int j = 1; j <= m; j++) {\n    dp[0][j] = j;\n  }\n  // State transition: rest of the rows and columns\n  for (int i = 1; i <= n; i++) {\n    for (int j = 1; j <= m; j++) {\n      if (s[i - 1] == t[j - 1]) {\n        // If two characters are equal, skip both characters\n        dp[i][j] = dp[i - 1][j - 1];\n      } else {\n        // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n        dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n      }\n    }\n  }\n  return dp[n][m];\n}\n
edit_distance.rs
/* Edit distance: Dynamic programming */\nfn edit_distance_dp(s: &str, t: &str) -> i32 {\n    let (n, m) = (s.len(), t.len());\n    let mut dp = vec![vec![0; m + 1]; n + 1];\n    // State transition: first row and first column\n    for i in 1..=n {\n        dp[i][0] = i as i32;\n    }\n    for j in 1..m {\n        dp[0][j] = j as i32;\n    }\n    // State transition: rest of the rows and columns\n    for i in 1..=n {\n        for j in 1..=m {\n            if s.chars().nth(i - 1) == t.chars().nth(j - 1) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] =\n                    std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    dp[n][m]\n}\n
edit_distance.c
/* Edit distance: Dynamic programming */\nint editDistanceDP(char *s, char *t, int n, int m) {\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(m + 1, sizeof(int));\n    }\n    // State transition: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    int res = dp[n][m];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
edit_distance.kt
/* Edit distance: Dynamic programming */\nfun editDistanceDP(s: String, t: String): Int {\n    val n = s.length\n    val m = t.length\n    val dp = Array(n + 1) { IntArray(m + 1) }\n    // State transition: first row and first column\n    for (i in 1..n) {\n        dp[i][0] = i\n    }\n    for (j in 1..m) {\n        dp[0][j] = j\n    }\n    // State transition: rest of the rows and columns\n    for (i in 1..n) {\n        for (j in 1..m) {\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1]\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
edit_distance.rb
### Edit distance: dynamic programming ###\ndef edit_distance_dp(s, t)\n  n, m = s.length, t.length\n  dp = Array.new(n + 1) { Array.new(m + 1, 0) }\n  # State transition: first row and first column\n  (1...(n + 1)).each { |i| dp[i][0] = i }\n  (1...(m + 1)).each { |j| dp[0][j] = j }\n  # State transition: rest of the rows and columns\n  for i in 1...(n + 1)\n    for j in 1...(m +1)\n      if s[i - 1] == t[j - 1]\n        # If two characters are equal, skip both characters\n        dp[i][j] = dp[i - 1][j - 1]\n      else\n        # Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n        dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1\n      end\n    end\n  end\n  dp[n][m]\nend\n

As shown in Figure 14-30, the state transition process for the edit distance problem is very similar to the knapsack problem and can both be viewed as the process of filling a two-dimensional grid.

<1><2><3><4><5><6><7><8><9><10><11><12><13><14><15>

Figure 14-30   Dynamic programming process for edit distance

","path":["Chapter 14. Dynamic Programming","14.6   Edit Distance Problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#3-space-optimization","level":3,"title":"3.   Space Optimization","text":"

Since \\(dp[i, j]\\) is transferred from the solutions above \\(dp[i-1, j]\\), to the left \\(dp[i, j-1]\\), and to the upper-left \\(dp[i-1, j-1]\\), forward traversal will lose the upper-left solution \\(dp[i-1, j-1]\\), and reverse traversal cannot build \\(dp[i, j-1]\\) in advance, so neither traversal order is feasible.

For this reason, we can use a variable leftup to temporarily store the upper-left solution \\(dp[i-1, j-1]\\), so we only need to consider the solutions to the left and above. This situation is the same as the unbounded knapsack problem, allowing for forward traversal. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby edit_distance.py
def edit_distance_dp_comp(s: str, t: str) -> int:\n    \"\"\"Edit distance: Space-optimized dynamic programming\"\"\"\n    n, m = len(s), len(t)\n    dp = [0] * (m + 1)\n    # State transition: first row\n    for j in range(1, m + 1):\n        dp[j] = j\n    # State transition: rest of the rows\n    for i in range(1, n + 1):\n        # State transition: first column\n        leftup = dp[0]  # Temporarily store dp[i-1, j-1]\n        dp[0] += 1\n        # State transition: rest of the columns\n        for j in range(1, m + 1):\n            temp = dp[j]\n            if s[i - 1] == t[j - 1]:\n                # If two characters are equal, skip both characters\n                dp[j] = leftup\n            else:\n                # Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = min(dp[j - 1], dp[j], leftup) + 1\n            leftup = temp  # Update for next round's dp[i-1, j-1]\n    return dp[m]\n
edit_distance.cpp
/* Edit distance: Space-optimized dynamic programming */\nint editDistanceDPComp(string s, string t) {\n    int n = s.length(), m = t.length();\n    vector<int> dp(m + 1, 0);\n    // State transition: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i <= n; i++) {\n        // State transition: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m];\n}\n
edit_distance.java
/* Edit distance: Space-optimized dynamic programming */\nint editDistanceDPComp(String s, String t) {\n    int n = s.length(), m = t.length();\n    int[] dp = new int[m + 1];\n    // State transition: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i <= n; i++) {\n        // State transition: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s.charAt(i - 1) == t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m];\n}\n
edit_distance.cs
/* Edit distance: Space-optimized dynamic programming */\nint EditDistanceDPComp(string s, string t) {\n    int n = s.Length, m = t.Length;\n    int[] dp = new int[m + 1];\n    // State transition: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i <= n; i++) {\n        // State transition: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m];\n}\n
edit_distance.go
/* Edit distance: Space-optimized dynamic programming */\nfunc editDistanceDPComp(s string, t string) int {\n    n := len(s)\n    m := len(t)\n    dp := make([]int, m+1)\n    // State transition: first row\n    for j := 1; j <= m; j++ {\n        dp[j] = j\n    }\n    // State transition: rest of the rows\n    for i := 1; i <= n; i++ {\n        // State transition: first column\n        leftUp := dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transition: rest of the columns\n        for j := 1; j <= m; j++ {\n            temp := dp[j]\n            if s[i-1] == t[j-1] {\n                // If two characters are equal, skip both characters\n                dp[j] = leftUp\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1\n            }\n            leftUp = temp // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m]\n}\n
edit_distance.swift
/* Edit distance: Space-optimized dynamic programming */\nfunc editDistanceDPComp(s: String, t: String) -> Int {\n    let n = s.utf8CString.count\n    let m = t.utf8CString.count\n    var dp = Array(repeating: 0, count: m + 1)\n    // State transition: first row\n    for j in 1 ... m {\n        dp[j] = j\n    }\n    // State transition: rest of the rows\n    for i in 1 ... n {\n        // State transition: first column\n        var leftup = dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transition: rest of the columns\n        for j in 1 ... m {\n            let temp = dp[j]\n            if s.utf8CString[i - 1] == t.utf8CString[j - 1] {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1\n            }\n            leftup = temp // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m]\n}\n
edit_distance.js
/* Edit distance: Space-optimized dynamic programming */\nfunction editDistanceDPComp(s, t) {\n    const n = s.length,\n        m = t.length;\n    const dp = new Array(m + 1).fill(0);\n    // State transition: first row\n    for (let j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (let i = 1; i <= n; i++) {\n        // State transition: first column\n        let leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (let j = 1; j <= m; j++) {\n            const temp = dp[j];\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m];\n}\n
edit_distance.ts
/* Edit distance: Space-optimized dynamic programming */\nfunction editDistanceDPComp(s: string, t: string): number {\n    const n = s.length,\n        m = t.length;\n    const dp = new Array(m + 1).fill(0);\n    // State transition: first row\n    for (let j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (let i = 1; i <= n; i++) {\n        // State transition: first column\n        let leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (let j = 1; j <= m; j++) {\n            const temp = dp[j];\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m];\n}\n
edit_distance.dart
/* Edit distance: Space-optimized dynamic programming */\nint editDistanceDPComp(String s, String t) {\n  int n = s.length, m = t.length;\n  List<int> dp = List.filled(m + 1, 0);\n  // State transition: first row\n  for (int j = 1; j <= m; j++) {\n    dp[j] = j;\n  }\n  // State transition: rest of the rows\n  for (int i = 1; i <= n; i++) {\n    // State transition: first column\n    int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n    dp[0] = i;\n    // State transition: rest of the columns\n    for (int j = 1; j <= m; j++) {\n      int temp = dp[j];\n      if (s[i - 1] == t[j - 1]) {\n        // If two characters are equal, skip both characters\n        dp[j] = leftup;\n      } else {\n        // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n        dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;\n      }\n      leftup = temp; // Update for next round's dp[i-1, j-1]\n    }\n  }\n  return dp[m];\n}\n
edit_distance.rs
/* Edit distance: Space-optimized dynamic programming */\nfn edit_distance_dp_comp(s: &str, t: &str) -> i32 {\n    let (n, m) = (s.len(), t.len());\n    let mut dp = vec![0; m + 1];\n    // State transition: first row\n    for j in 1..m {\n        dp[j] = j as i32;\n    }\n    // State transition: rest of the rows\n    for i in 1..=n {\n        // State transition: first column\n        let mut leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i as i32;\n        // State transition: rest of the columns\n        for j in 1..=m {\n            let temp = dp[j];\n            if s.chars().nth(i - 1) == t.chars().nth(j - 1) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    dp[m]\n}\n
edit_distance.c
/* Edit distance: Space-optimized dynamic programming */\nint editDistanceDPComp(char *s, char *t, int n, int m) {\n    int *dp = calloc(m + 1, sizeof(int));\n    // State transition: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i <= n; i++) {\n        // State transition: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    int res = dp[m];\n    // Free memory\n    free(dp);\n    return res;\n}\n
edit_distance.kt
/* Edit distance: Space-optimized dynamic programming */\nfun editDistanceDPComp(s: String, t: String): Int {\n    val n = s.length\n    val m = t.length\n    val dp = IntArray(m + 1)\n    // State transition: first row\n    for (j in 1..m) {\n        dp[j] = j\n    }\n    // State transition: rest of the rows\n    for (i in 1..n) {\n        // State transition: first column\n        var leftup = dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transition: rest of the columns\n        for (j in 1..m) {\n            val temp = dp[j]\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1\n            }\n            leftup = temp // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m]\n}\n
edit_distance.rb
### Edit distance: space-optimized DP ###\ndef edit_distance_dp_comp(s, t)\n  n, m = s.length, t.length\n  dp = Array.new(m + 1, 0)\n  # State transition: first row\n  (1...(m + 1)).each { |j| dp[j] = j }\n  # State transition: rest of the rows\n  for i in 1...(n + 1)\n    # State transition: first column\n    leftup = dp.first # Temporarily store dp[i-1, j-1]\n    dp[0] += 1\n    # State transition: rest of the columns\n    for j in 1...(m + 1)\n      temp = dp[j]\n      if s[i - 1] == t[j - 1]\n        # If two characters are equal, skip both characters\n        dp[j] = leftup\n      else\n        # Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n        dp[j] = [dp[j - 1], dp[j], leftup].min + 1\n      end\n      leftup = temp # Update for next round's dp[i-1, j-1]\n    end\n  end\n  dp[m]\nend\n
","path":["Chapter 14. Dynamic Programming","14.6   Edit Distance Problem"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/","level":1,"title":"14.1   Introduction to Dynamic Programming","text":"

Dynamic programming is an important algorithmic paradigm that decomposes a problem into a series of smaller subproblems and avoids redundant computation by storing the solutions to subproblems, thereby significantly improving time efficiency.

In this section, we start with a classic example, first presenting its brute force backtracking solution, observing the overlapping subproblems within it, and then gradually deriving a more efficient dynamic programming solution.

Climbing stairs

Given a staircase with \\(n\\) steps, where you can climb \\(1\\) or \\(2\\) steps at a time, how many different ways are there to reach the top?

As shown in Figure 14-1, for a \\(3\\)-step staircase, there are \\(3\\) different ways to reach the top.

Figure 14-1   Number of ways to reach the 3rd step

The goal of this problem is to find the number of ways, we can consider using backtracking to enumerate all possibilities. Specifically, imagine climbing stairs as a multi-round selection process: starting from the ground, choosing to go up \\(1\\) or \\(2\\) steps in each round, incrementing the count by \\(1\\) whenever the top of the stairs is reached, and pruning when exceeding the top. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_backtrack.py
def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int:\n    \"\"\"Backtracking\"\"\"\n    # When climbing to the n-th stair, add 1 to the solution count\n    if state == n:\n        res[0] += 1\n    # Traverse all choices\n    for choice in choices:\n        # Pruning: not allowed to go beyond the n-th stair\n        if state + choice > n:\n            continue\n        # Attempt: make a choice, update state\n        backtrack(choices, state + choice, n, res)\n        # Backtrack\n\ndef climbing_stairs_backtrack(n: int) -> int:\n    \"\"\"Climbing stairs: Backtracking\"\"\"\n    choices = [1, 2]  # Can choose to climb up 1 or 2 stairs\n    state = 0  # Start climbing from the 0-th stair\n    res = [0]  # Use res[0] to record the solution count\n    backtrack(choices, state, n, res)\n    return res[0]\n
climbing_stairs_backtrack.cpp
/* Backtracking */\nvoid backtrack(vector<int> &choices, int state, int n, vector<int> &res) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state == n)\n        res[0]++;\n    // Traverse all choices\n    for (auto &choice : choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n)\n            continue;\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nint climbingStairsBacktrack(int n) {\n    vector<int> choices = {1, 2}; // Can choose to climb up 1 or 2 stairs\n    int state = 0;                // Start climbing from the 0-th stair\n    vector<int> res = {0};        // Use res[0] to record the solution count\n    backtrack(choices, state, n, res);\n    return res[0];\n}\n
climbing_stairs_backtrack.java
/* Backtracking */\nvoid backtrack(List<Integer> choices, int state, int n, List<Integer> res) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state == n)\n        res.set(0, res.get(0) + 1);\n    // Traverse all choices\n    for (Integer choice : choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n)\n            continue;\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nint climbingStairsBacktrack(int n) {\n    List<Integer> choices = Arrays.asList(1, 2); // Can choose to climb up 1 or 2 stairs\n    int state = 0; // Start climbing from the 0-th stair\n    List<Integer> res = new ArrayList<>();\n    res.add(0); // Use res[0] to record the solution count\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
climbing_stairs_backtrack.cs
/* Backtracking */\nvoid Backtrack(List<int> choices, int state, int n, List<int> res) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state == n)\n        res[0]++;\n    // Traverse all choices\n    foreach (int choice in choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n)\n            continue;\n        // Attempt: make choice, update state\n        Backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nint ClimbingStairsBacktrack(int n) {\n    List<int> choices = [1, 2]; // Can choose to climb up 1 or 2 stairs\n    int state = 0; // Start climbing from the 0-th stair\n    List<int> res = [0]; // Use res[0] to record the solution count\n    Backtrack(choices, state, n, res);\n    return res[0];\n}\n
climbing_stairs_backtrack.go
/* Backtracking */\nfunc backtrack(choices []int, state, n int, res []int) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if state == n {\n        res[0] = res[0] + 1\n    }\n    // Traverse all choices\n    for _, choice := range choices {\n        // Pruning: not allowed to go beyond the n-th stair\n        if state+choice > n {\n            continue\n        }\n        // Attempt: make choice, update state\n        backtrack(choices, state+choice, n, res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfunc climbingStairsBacktrack(n int) int {\n    // Can choose to climb up 1 or 2 stairs\n    choices := []int{1, 2}\n    // Start climbing from the 0-th stair\n    state := 0\n    res := make([]int, 1)\n    // Use res[0] to record the solution count\n    res[0] = 0\n    backtrack(choices, state, n, res)\n    return res[0]\n}\n
climbing_stairs_backtrack.swift
/* Backtracking */\nfunc backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if state == n {\n        res[0] += 1\n    }\n    // Traverse all choices\n    for choice in choices {\n        // Pruning: not allowed to go beyond the n-th stair\n        if state + choice > n {\n            continue\n        }\n        // Attempt: make choice, update state\n        backtrack(choices: choices, state: state + choice, n: n, res: &res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfunc climbingStairsBacktrack(n: Int) -> Int {\n    let choices = [1, 2] // Can choose to climb up 1 or 2 stairs\n    let state = 0 // Start climbing from the 0-th stair\n    var res: [Int] = []\n    res.append(0) // Use res[0] to record the solution count\n    backtrack(choices: choices, state: state, n: n, res: &res)\n    return res[0]\n}\n
climbing_stairs_backtrack.js
/* Backtracking */\nfunction backtrack(choices, state, n, res) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state === n) res.set(0, res.get(0) + 1);\n    // Traverse all choices\n    for (const choice of choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n) continue;\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfunction climbingStairsBacktrack(n) {\n    const choices = [1, 2]; // Can choose to climb up 1 or 2 stairs\n    const state = 0; // Start climbing from the 0-th stair\n    const res = new Map();\n    res.set(0, 0); // Use res[0] to record the solution count\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
climbing_stairs_backtrack.ts
/* Backtracking */\nfunction backtrack(\n    choices: number[],\n    state: number,\n    n: number,\n    res: Map<0, any>\n): void {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state === n) res.set(0, res.get(0) + 1);\n    // Traverse all choices\n    for (const choice of choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n) continue;\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfunction climbingStairsBacktrack(n: number): number {\n    const choices = [1, 2]; // Can choose to climb up 1 or 2 stairs\n    const state = 0; // Start climbing from the 0-th stair\n    const res = new Map();\n    res.set(0, 0); // Use res[0] to record the solution count\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
climbing_stairs_backtrack.dart
/* Backtracking */\nvoid backtrack(List<int> choices, int state, int n, List<int> res) {\n  // When climbing to the n-th stair, add 1 to the solution count\n  if (state == n) {\n    res[0]++;\n  }\n  // Traverse all choices\n  for (int choice in choices) {\n    // Pruning: not allowed to go beyond the n-th stair\n    if (state + choice > n) continue;\n    // Attempt: make choice, update state\n    backtrack(choices, state + choice, n, res);\n    // Backtrack\n  }\n}\n\n/* Climbing stairs: Backtracking */\nint climbingStairsBacktrack(int n) {\n  List<int> choices = [1, 2]; // Can choose to climb up 1 or 2 stairs\n  int state = 0; // Start climbing from the 0-th stair\n  List<int> res = [];\n  res.add(0); // Use res[0] to record the solution count\n  backtrack(choices, state, n, res);\n  return res[0];\n}\n
climbing_stairs_backtrack.rs
/* Backtracking */\nfn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if state == n {\n        res[0] = res[0] + 1;\n    }\n    // Traverse all choices\n    for &choice in choices {\n        // Pruning: not allowed to go beyond the n-th stair\n        if state + choice > n {\n            continue;\n        }\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfn climbing_stairs_backtrack(n: usize) -> i32 {\n    let choices = vec![1, 2]; // Can choose to climb up 1 or 2 stairs\n    let state = 0; // Start climbing from the 0-th stair\n    let mut res = Vec::new();\n    res.push(0); // Use res[0] to record the solution count\n    backtrack(&choices, state, n as i32, &mut res);\n    res[0]\n}\n
climbing_stairs_backtrack.c
/* Backtracking */\nvoid backtrack(int *choices, int state, int n, int *res, int len) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state == n)\n        res[0]++;\n    // Traverse all choices\n    for (int i = 0; i < len; i++) {\n        int choice = choices[i];\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n)\n            continue;\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res, len);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nint climbingStairsBacktrack(int n) {\n    int choices[2] = {1, 2}; // Can choose to climb up 1 or 2 stairs\n    int state = 0;           // Start climbing from the 0-th stair\n    int *res = (int *)malloc(sizeof(int));\n    *res = 0; // Use res[0] to record the solution count\n    int len = sizeof(choices) / sizeof(int);\n    backtrack(choices, state, n, res, len);\n    int result = *res;\n    free(res);\n    return result;\n}\n
climbing_stairs_backtrack.kt
/* Backtracking */\nfun backtrack(\n    choices: MutableList<Int>,\n    state: Int,\n    n: Int,\n    res: MutableList<Int>\n) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state == n)\n        res[0] = res[0] + 1\n    // Traverse all choices\n    for (choice in choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n) continue\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfun climbingStairsBacktrack(n: Int): Int {\n    val choices = mutableListOf(1, 2) // Can choose to climb up 1 or 2 stairs\n    val state = 0 // Start climbing from the 0-th stair\n    val res = mutableListOf<Int>()\n    res.add(0) // Use res[0] to record the solution count\n    backtrack(choices, state, n, res)\n    return res[0]\n}\n
climbing_stairs_backtrack.rb
### Backtracking ###\ndef backtrack(choices, state, n, res)\n  # When climbing to the n-th stair, add 1 to the solution count\n  res[0] += 1 if state == n\n  # Traverse all choices\n  for choice in choices\n    # Pruning: not allowed to go beyond the n-th stair\n    next if state + choice > n\n\n    # Attempt: make choice, update state\n    backtrack(choices, state + choice, n, res)\n  end\n  # Backtrack\nend\n\n### Climbing stairs: backtracking ###\ndef climbing_stairs_backtrack(n)\n  choices = [1, 2] # Can choose to climb up 1 or 2 stairs\n  state = 0 # Start climbing from the 0-th stair\n  res = [0] # Use res[0] to record the solution count\n  backtrack(choices, state, n, res)\n  res.first\nend\n
","path":["Chapter 14. Dynamic Programming","14.1   Introduction to Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1411-method-1-brute-force-search","level":2,"title":"14.1.1   Method 1: Brute Force Search","text":"

Backtracking algorithms typically do not explicitly decompose problems, but rather treat solving the problem as a series of decision steps, searching for all possible solutions through trial and pruning.

We can try to analyze this problem from the perspective of problem decomposition. Let the number of ways to climb to the \\(i\\)-th step be \\(dp[i]\\), then \\(dp[i]\\) is the original problem, and its subproblems include:

\\[ dp[i-1], dp[i-2], \\dots, dp[2], dp[1] \\]

Since we can only go up \\(1\\) or \\(2\\) steps in each round, when we stand on the \\(i\\)-th step, we could only have been on the \\(i-1\\)-th or \\(i-2\\)-th step in the previous round. In other words, we can only reach the \\(i\\)-th step from the \\(i-1\\)-th or \\(i-2\\)-th step.

This leads to an important conclusion: the number of ways to climb to the \\(i-1\\)-th step plus the number of ways to climb to the \\(i-2\\)-th step equals the number of ways to climb to the \\(i\\)-th step. The formula is as follows:

\\[ dp[i] = dp[i-1] + dp[i-2] \\]

This means that in the stair climbing problem, there exists a recurrence relation among the subproblems, the solution to the original problem can be constructed from the solutions to the subproblems. Figure 14-2 illustrates this recurrence relation.

Figure 14-2   Recurrence relation for the number of ways

We can obtain a brute force search solution based on the recurrence formula. Starting from \\(dp[n]\\), recursively decompose a larger problem into the sum of two smaller problems, until reaching the smallest subproblems \\(dp[1]\\) and \\(dp[2]\\) and returning. Among them, the solutions to the smallest subproblems are known, namely \\(dp[1] = 1\\) and \\(dp[2] = 2\\), representing \\(1\\) and \\(2\\) ways to climb to the \\(1\\)st and \\(2\\)nd steps, respectively.

Observe the following code, which, like standard backtracking code, belongs to depth-first search but is more concise:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dfs.py
def dfs(i: int) -> int:\n    \"\"\"Search\"\"\"\n    # Known dp[1] and dp[2], return them\n    if i == 1 or i == 2:\n        return i\n    # dp[i] = dp[i-1] + dp[i-2]\n    count = dfs(i - 1) + dfs(i - 2)\n    return count\n\ndef climbing_stairs_dfs(n: int) -> int:\n    \"\"\"Climbing stairs: Search\"\"\"\n    return dfs(n)\n
climbing_stairs_dfs.cpp
/* Search */\nint dfs(int i) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
climbing_stairs_dfs.java
/* Search */\nint dfs(int i) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
climbing_stairs_dfs.cs
/* Search */\nint DFS(int i) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = DFS(i - 1) + DFS(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nint ClimbingStairsDFS(int n) {\n    return DFS(n);\n}\n
climbing_stairs_dfs.go
/* Search */\nfunc dfs(i int) int {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    count := dfs(i-1) + dfs(i-2)\n    return count\n}\n\n/* Climbing stairs: Search */\nfunc climbingStairsDFS(n int) int {\n    return dfs(n)\n}\n
climbing_stairs_dfs.swift
/* Search */\nfunc dfs(i: Int) -> Int {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i: i - 1) + dfs(i: i - 2)\n    return count\n}\n\n/* Climbing stairs: Search */\nfunc climbingStairsDFS(n: Int) -> Int {\n    dfs(i: n)\n}\n
climbing_stairs_dfs.js
/* Search */\nfunction dfs(i) {\n    // Known dp[1] and dp[2], return them\n    if (i === 1 || i === 2) return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nfunction climbingStairsDFS(n) {\n    return dfs(n);\n}\n
climbing_stairs_dfs.ts
/* Search */\nfunction dfs(i: number): number {\n    // Known dp[1] and dp[2], return them\n    if (i === 1 || i === 2) return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nfunction climbingStairsDFS(n: number): number {\n    return dfs(n);\n}\n
climbing_stairs_dfs.dart
/* Search */\nint dfs(int i) {\n  // Known dp[1] and dp[2], return them\n  if (i == 1 || i == 2) return i;\n  // dp[i] = dp[i-1] + dp[i-2]\n  int count = dfs(i - 1) + dfs(i - 2);\n  return count;\n}\n\n/* Climbing stairs: Search */\nint climbingStairsDFS(int n) {\n  return dfs(n);\n}\n
climbing_stairs_dfs.rs
/* Search */\nfn dfs(i: usize) -> i32 {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i as i32;\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i - 1) + dfs(i - 2);\n    count\n}\n\n/* Climbing stairs: Search */\nfn climbing_stairs_dfs(n: usize) -> i32 {\n    dfs(n)\n}\n
climbing_stairs_dfs.c
/* Search */\nint dfs(int i) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
climbing_stairs_dfs.kt
/* Search */\nfun dfs(i: Int): Int {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2) return i\n    // dp[i] = dp[i-1] + dp[i-2]\n    val count = dfs(i - 1) + dfs(i - 2)\n    return count\n}\n\n/* Climbing stairs: Search */\nfun climbingStairsDFS(n: Int): Int {\n    return dfs(n)\n}\n
climbing_stairs_dfs.rb
### Search ###\ndef dfs(i)\n  # Known dp[1] and dp[2], return them\n  return i if i == 1 || i == 2\n  # dp[i] = dp[i-1] + dp[i-2]\n  dfs(i - 1) + dfs(i - 2)\nend\n\n### Climbing stairs: search ###\ndef climbing_stairs_dfs(n)\n  dfs(n)\nend\n

Figure 14-3 shows the recursion tree formed by brute force search. For the problem \\(dp[n]\\), the depth of its recursion tree is \\(n\\), with a time complexity of \\(O(2^n)\\). Exponential order represents explosive growth; if we input a relatively large \\(n\\), we will fall into a long wait.

Figure 14-3   Recursion tree for climbing stairs

Observing the above figure, the exponential time complexity is caused by \"overlapping subproblems\". For example, \\(dp[9]\\) is decomposed into \\(dp[8]\\) and \\(dp[7]\\), and \\(dp[8]\\) is decomposed into \\(dp[7]\\) and \\(dp[6]\\), both of which contain the subproblem \\(dp[7]\\).

And so on, subproblems contain smaller overlapping subproblems, ad infinitum. The vast majority of computational resources are wasted on these overlapping subproblems.

","path":["Chapter 14. Dynamic Programming","14.1   Introduction to Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1412-method-2-memoization","level":2,"title":"14.1.2   Method 2: Memoization","text":"

To improve algorithm efficiency, we want all overlapping subproblems to be computed only once. For this purpose, we declare an array mem to record the solution to each subproblem and prune overlapping subproblems during the search process.

  1. When computing \\(dp[i]\\) for the first time, we record it in mem[i] for later use.
  2. When we need to compute \\(dp[i]\\) again, we can directly retrieve the result from mem[i], thereby avoiding redundant computation of that subproblem.

The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dfs_mem.py
def dfs(i: int, mem: list[int]) -> int:\n    \"\"\"Memoization search\"\"\"\n    # Known dp[1] and dp[2], return them\n    if i == 1 or i == 2:\n        return i\n    # If record dp[i] exists, return it directly\n    if mem[i] != -1:\n        return mem[i]\n    # dp[i] = dp[i-1] + dp[i-2]\n    count = dfs(i - 1, mem) + dfs(i - 2, mem)\n    # Record dp[i]\n    mem[i] = count\n    return count\n\ndef climbing_stairs_dfs_mem(n: int) -> int:\n    \"\"\"Climbing stairs: Memoization search\"\"\"\n    # mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    mem = [-1] * (n + 1)\n    return dfs(n, mem)\n
climbing_stairs_dfs_mem.cpp
/* Memoization search */\nint dfs(int i, vector<int> &mem) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    vector<int> mem(n + 1, -1);\n    return dfs(n, mem);\n}\n
climbing_stairs_dfs_mem.java
/* Memoization search */\nint dfs(int i, int[] mem) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    int[] mem = new int[n + 1];\n    Arrays.fill(mem, -1);\n    return dfs(n, mem);\n}\n
climbing_stairs_dfs_mem.cs
/* Memoization search */\nint DFS(int i, int[] mem) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = DFS(i - 1, mem) + DFS(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nint ClimbingStairsDFSMem(int n) {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    int[] mem = new int[n + 1];\n    Array.Fill(mem, -1);\n    return DFS(n, mem);\n}\n
climbing_stairs_dfs_mem.go
/* Memoization search */\nfunc dfsMem(i int, mem []int) int {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // If record dp[i] exists, return it directly\n    if mem[i] != -1 {\n        return mem[i]\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    count := dfsMem(i-1, mem) + dfsMem(i-2, mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: Memoization search */\nfunc climbingStairsDFSMem(n int) int {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    mem := make([]int, n+1)\n    for i := range mem {\n        mem[i] = -1\n    }\n    return dfsMem(n, mem)\n}\n
climbing_stairs_dfs_mem.swift
/* Memoization search */\nfunc dfs(i: Int, mem: inout [Int]) -> Int {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // If record dp[i] exists, return it directly\n    if mem[i] != -1 {\n        return mem[i]\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: Memoization search */\nfunc climbingStairsDFSMem(n: Int) -> Int {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    var mem = Array(repeating: -1, count: n + 1)\n    return dfs(i: n, mem: &mem)\n}\n
climbing_stairs_dfs_mem.js
/* Memoization search */\nfunction dfs(i, mem) {\n    // Known dp[1] and dp[2], return them\n    if (i === 1 || i === 2) return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1) return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nfunction climbingStairsDFSMem(n) {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    const mem = new Array(n + 1).fill(-1);\n    return dfs(n, mem);\n}\n
climbing_stairs_dfs_mem.ts
/* Memoization search */\nfunction dfs(i: number, mem: number[]): number {\n    // Known dp[1] and dp[2], return them\n    if (i === 1 || i === 2) return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1) return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nfunction climbingStairsDFSMem(n: number): number {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    const mem = new Array(n + 1).fill(-1);\n    return dfs(n, mem);\n}\n
climbing_stairs_dfs_mem.dart
/* Memoization search */\nint dfs(int i, List<int> mem) {\n  // Known dp[1] and dp[2], return them\n  if (i == 1 || i == 2) return i;\n  // If record dp[i] exists, return it directly\n  if (mem[i] != -1) return mem[i];\n  // dp[i] = dp[i-1] + dp[i-2]\n  int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n  // Record dp[i]\n  mem[i] = count;\n  return count;\n}\n\n/* Climbing stairs: Memoization search */\nint climbingStairsDFSMem(int n) {\n  // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n  List<int> mem = List.filled(n + 1, -1);\n  return dfs(n, mem);\n}\n
climbing_stairs_dfs_mem.rs
/* Memoization search */\nfn dfs(i: usize, mem: &mut [i32]) -> i32 {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i as i32;\n    }\n    // If record dp[i] exists, return it directly\n    if mem[i] != -1 {\n        return mem[i];\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    count\n}\n\n/* Climbing stairs: Memoization search */\nfn climbing_stairs_dfs_mem(n: usize) -> i32 {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    let mut mem = vec![-1; n + 1];\n    dfs(n, &mut mem)\n}\n
climbing_stairs_dfs_mem.c
/* Memoization search */\nint dfs(int i, int *mem) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    int *mem = (int *)malloc((n + 1) * sizeof(int));\n    for (int i = 0; i <= n; i++) {\n        mem[i] = -1;\n    }\n    int result = dfs(n, mem);\n    free(mem);\n    return result;\n}\n
climbing_stairs_dfs_mem.kt
/* Memoization search */\nfun dfs(i: Int, mem: IntArray): Int {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2) return i\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1) return mem[i]\n    // dp[i] = dp[i-1] + dp[i-2]\n    val count = dfs(i - 1, mem) + dfs(i - 2, mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: Memoization search */\nfun climbingStairsDFSMem(n: Int): Int {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    val mem = IntArray(n + 1)\n    mem.fill(-1)\n    return dfs(n, mem)\n}\n
climbing_stairs_dfs_mem.rb
### Memoization search ###\ndef dfs(i, mem)\n  # Known dp[1] and dp[2], return them\n  return i if i == 1 || i == 2\n  # If record dp[i] exists, return it directly\n  return mem[i] if mem[i] != -1\n\n  # dp[i] = dp[i-1] + dp[i-2]\n  count = dfs(i - 1, mem) + dfs(i - 2, mem)\n  # Record dp[i]\n  mem[i] = count\nend\n\n### Climbing stairs: memoization search ###\ndef climbing_stairs_dfs_mem(n)\n  # mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n  mem = Array.new(n + 1, -1)\n  dfs(n, mem)\nend\n

Observe Figure 14-4, after memoization, all overlapping subproblems only need to be computed once, optimizing the time complexity to \\(O(n)\\), which is a tremendous leap.

Figure 14-4   Recursion tree with memoization

","path":["Chapter 14. Dynamic Programming","14.1   Introduction to Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1413-method-3-dynamic-programming","level":2,"title":"14.1.3   Method 3: Dynamic Programming","text":"

Memoization is a \"top-down\" method: we start from the original problem (root node), recursively decompose larger subproblems into smaller ones, until reaching the smallest known subproblems (leaf nodes). Afterward, by backtracking, we collect the solutions to the subproblems layer by layer to construct the solution to the original problem.

In contrast, dynamic programming is a \"bottom-up\" method: starting from the solutions to the smallest subproblems, iteratively constructing solutions to larger subproblems until obtaining the solution to the original problem.

Since dynamic programming does not include a backtracking process, it only requires loop iteration for implementation and does not need recursion. In the following code, we initialize an array dp to store the solutions to subproblems, which serves the same recording function as the array mem in memoization:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dp.py
def climbing_stairs_dp(n: int) -> int:\n    \"\"\"Climbing stairs: Dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return n\n    # Initialize dp table, used to store solutions to subproblems\n    dp = [0] * (n + 1)\n    # Initial state: preset the solution to the smallest subproblem\n    dp[1], dp[2] = 1, 2\n    # State transition: gradually solve larger subproblems from smaller ones\n    for i in range(3, n + 1):\n        dp[i] = dp[i - 1] + dp[i - 2]\n    return dp[n]\n
climbing_stairs_dp.cpp
/* Climbing stairs: Dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize dp table, used to store solutions to subproblems\n    vector<int> dp(n + 1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
climbing_stairs_dp.java
/* Climbing stairs: Dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize dp table, used to store solutions to subproblems\n    int[] dp = new int[n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
climbing_stairs_dp.cs
/* Climbing stairs: Dynamic programming */\nint ClimbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize dp table, used to store solutions to subproblems\n    int[] dp = new int[n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
climbing_stairs_dp.go
/* Climbing stairs: Dynamic programming */\nfunc climbingStairsDP(n int) int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    dp := make([]int, n+1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1\n    dp[2] = 2\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i] = dp[i-1] + dp[i-2]\n    }\n    return dp[n]\n}\n
climbing_stairs_dp.swift
/* Climbing stairs: Dynamic programming */\nfunc climbingStairsDP(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    var dp = Array(repeating: 0, count: n + 1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1\n    dp[2] = 2\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3 ... n {\n        dp[i] = dp[i - 1] + dp[i - 2]\n    }\n    return dp[n]\n}\n
climbing_stairs_dp.js
/* Climbing stairs: Dynamic programming */\nfunction climbingStairsDP(n) {\n    if (n === 1 || n === 2) return n;\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = new Array(n + 1).fill(-1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
climbing_stairs_dp.ts
/* Climbing stairs: Dynamic programming */\nfunction climbingStairsDP(n: number): number {\n    if (n === 1 || n === 2) return n;\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = new Array(n + 1).fill(-1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
climbing_stairs_dp.dart
/* Climbing stairs: Dynamic programming */\nint climbingStairsDP(int n) {\n  if (n == 1 || n == 2) return n;\n  // Initialize dp table, used to store solutions to subproblems\n  List<int> dp = List.filled(n + 1, 0);\n  // Initial state: preset the solution to the smallest subproblem\n  dp[1] = 1;\n  dp[2] = 2;\n  // State transition: gradually solve larger subproblems from smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i] = dp[i - 1] + dp[i - 2];\n  }\n  return dp[n];\n}\n
climbing_stairs_dp.rs
/* Climbing stairs: Dynamic programming */\nfn climbing_stairs_dp(n: usize) -> i32 {\n    // Known dp[1] and dp[2], return them\n    if n == 1 || n == 2 {\n        return n as i32;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    let mut dp = vec![-1; n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3..=n {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    dp[n]\n}\n
climbing_stairs_dp.c
/* Climbing stairs: Dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize dp table, used to store solutions to subproblems\n    int *dp = (int *)malloc((n + 1) * sizeof(int));\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    int result = dp[n];\n    free(dp);\n    return result;\n}\n
climbing_stairs_dp.kt
/* Climbing stairs: Dynamic programming */\nfun climbingStairsDP(n: Int): Int {\n    if (n == 1 || n == 2) return n\n    // Initialize dp table, used to store solutions to subproblems\n    val dp = IntArray(n + 1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1\n    dp[2] = 2\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (i in 3..n) {\n        dp[i] = dp[i - 1] + dp[i - 2]\n    }\n    return dp[n]\n}\n
climbing_stairs_dp.rb
### Climbing stairs: dynamic programming ###\ndef climbing_stairs_dp(n)\n  return n  if n == 1 || n == 2\n\n  # Initialize dp table, used to store solutions to subproblems\n  dp = Array.new(n + 1, 0)\n  # Initial state: preset the solution to the smallest subproblem\n  dp[1], dp[2] = 1, 2\n  # State transition: gradually solve larger subproblems from smaller ones\n  (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] }\n\n  dp[n]\nend\n

Figure 14-5 simulates the execution process of the above code.

Figure 14-5   Dynamic programming process for climbing stairs

Like backtracking algorithms, dynamic programming also uses the \"state\" concept to represent specific stages of problem solving, with each state corresponding to a subproblem and its corresponding local optimal solution. For example, the state in the stair climbing problem is defined as the current stair step number \\(i\\).

Based on the above content, we can summarize the commonly used terminology in dynamic programming.

  • The array dp is called the dp table, where \\(dp[i]\\) represents the solution to the subproblem corresponding to state \\(i\\).
  • The states corresponding to the smallest subproblems (the \\(1\\)st and \\(2\\)nd steps) are called initial states.
  • The recurrence formula \\(dp[i] = dp[i-1] + dp[i-2]\\) is called the state transition equation.
","path":["Chapter 14. Dynamic Programming","14.1   Introduction to Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1414-space-optimization","level":2,"title":"14.1.4   Space Optimization","text":"

Observant readers may have noticed that since \\(dp[i]\\) is only related to \\(dp[i-1]\\) and \\(dp[i-2]\\), we do not need to use an array dp to store the solutions to all subproblems, but can simply use two variables to roll forward. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dp.py
def climbing_stairs_dp_comp(n: int) -> int:\n    \"\"\"Climbing stairs: Space-optimized dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return n\n    a, b = 1, 2\n    for _ in range(3, n + 1):\n        a, b = b, a + b\n    return b\n
climbing_stairs_dp.cpp
/* Climbing stairs: Space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.java
/* Climbing stairs: Space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.cs
/* Climbing stairs: Space-optimized dynamic programming */\nint ClimbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.go
/* Climbing stairs: Space-optimized dynamic programming */\nfunc climbingStairsDPComp(n int) int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    a, b := 1, 2\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i := 3; i <= n; i++ {\n        a, b = b, a+b\n    }\n    return b\n}\n
climbing_stairs_dp.swift
/* Climbing stairs: Space-optimized dynamic programming */\nfunc climbingStairsDPComp(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    var a = 1\n    var b = 2\n    for _ in 3 ... n {\n        (a, b) = (b, a + b)\n    }\n    return b\n}\n
climbing_stairs_dp.js
/* Climbing stairs: Space-optimized dynamic programming */\nfunction climbingStairsDPComp(n) {\n    if (n === 1 || n === 2) return n;\n    let a = 1,\n        b = 2;\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.ts
/* Climbing stairs: Space-optimized dynamic programming */\nfunction climbingStairsDPComp(n: number): number {\n    if (n === 1 || n === 2) return n;\n    let a = 1,\n        b = 2;\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.dart
/* Climbing stairs: Space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n  if (n == 1 || n == 2) return n;\n  int a = 1, b = 2;\n  for (int i = 3; i <= n; i++) {\n    int tmp = b;\n    b = a + b;\n    a = tmp;\n  }\n  return b;\n}\n
climbing_stairs_dp.rs
/* Climbing stairs: Space-optimized dynamic programming */\nfn climbing_stairs_dp_comp(n: usize) -> i32 {\n    if n == 1 || n == 2 {\n        return n as i32;\n    }\n    let (mut a, mut b) = (1, 2);\n    for _ in 3..=n {\n        let tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    b\n}\n
climbing_stairs_dp.c
/* Climbing stairs: Space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.kt
/* Climbing stairs: Space-optimized dynamic programming */\nfun climbingStairsDPComp(n: Int): Int {\n    if (n == 1 || n == 2) return n\n    var a = 1\n    var b = 2\n    for (i in 3..n) {\n        val temp = b\n        b += a\n        a = temp\n    }\n    return b\n}\n
climbing_stairs_dp.rb
### Climbing stairs: space-optimized DP ###\ndef climbing_stairs_dp_comp(n)\n  return n if n == 1 || n == 2\n\n  a, b = 1, 2\n  (3...(n + 1)).each { a, b = b, a + b }\n\n  b\nend\n

Observing the above code, since the space occupied by the array dp is saved, the space complexity is reduced from \\(O(n)\\) to \\(O(1)\\).

In dynamic programming problems, the current state often depends only on a limited number of preceding states, allowing us to retain only the necessary states and save memory space through \"dimension reduction\". This space optimization technique is called \"rolling variable\" or \"rolling array\".

","path":["Chapter 14. Dynamic Programming","14.1   Introduction to Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/","level":1,"title":"14.4   0-1 Knapsack Problem","text":"

The knapsack problem is an excellent introductory problem for dynamic programming and is one of the most common problem forms in dynamic programming. It has many variants, such as the 0-1 knapsack problem, the unbounded knapsack problem, and the multiple knapsack problem.

In this section, we will first solve the most common 0-1 knapsack problem.

Question

Given \\(n\\) items, where the weight of the \\(i\\)-th item is \\(wgt[i-1]\\) and its value is \\(val[i-1]\\), and a knapsack with capacity \\(cap\\). Each item can only be selected once. What is the maximum value that can be placed in the knapsack within the capacity limit?

Observe Figure 14-17. Since item number \\(i\\) starts counting from \\(1\\) and array indices start from \\(0\\), item \\(i\\) corresponds to weight \\(wgt[i-1]\\) and value \\(val[i-1]\\).

Figure 14-17   Example data for 0-1 knapsack

We can view the 0-1 knapsack problem as a process consisting of \\(n\\) rounds of decisions, where for each item there are two decisions: not putting it in and putting it in, thus the problem satisfies the decision tree model.

The goal of this problem is to find \"the maximum value that can be placed in the knapsack within the capacity limit\", so it is more likely to be a dynamic programming problem.

Step 1: Think about the decisions in each round, define the state, and thus obtain the \\(dp\\) table

For each item, if not placed in the knapsack, the knapsack capacity remains unchanged; if placed in, the knapsack capacity decreases. From this, we can derive the state definition: current item number \\(i\\) and knapsack capacity \\(c\\), denoted as \\([i, c]\\).

State \\([i, c]\\) corresponds to the subproblem: the maximum value among the first \\(i\\) items in a knapsack of capacity \\(c\\), denoted as \\(dp[i, c]\\).

What we need to find is \\(dp[n, cap]\\), so we need a two-dimensional \\(dp\\) table of size \\((n+1) \\times (cap+1)\\).

Step 2: Identify the optimal substructure, and then derive the state transition equation

After making the decision for item \\(i\\), what remains is the subproblem of the first \\(i-1\\) items, which can be divided into the following two cases.

  • Not putting item \\(i\\): The knapsack capacity remains unchanged, and the state changes to \\([i-1, c]\\).
  • Putting item \\(i\\): The knapsack capacity decreases by \\(wgt[i-1]\\), the value increases by \\(val[i-1]\\), and the state changes to \\([i-1, c-wgt[i-1]]\\).

The above analysis reveals the optimal substructure of this problem: the maximum value \\(dp[i, c]\\) equals the larger value between not putting item \\(i\\) and putting item \\(i\\). From this, the state transition equation can be derived:

\\[ dp[i, c] = \\max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) \\]

Note that if the weight of the current item \\(wgt[i - 1]\\) exceeds the remaining knapsack capacity \\(c\\), then the only option is not to put it in the knapsack.

Step 3: Determine boundary conditions and state transition order

When there are no items or the knapsack capacity is \\(0\\), the maximum value is \\(0\\), i.e., the first column \\(dp[i, 0]\\) and the first row \\(dp[0, c]\\) are both equal to \\(0\\).

The current state \\([i, c]\\) is transferred from the state above \\([i-1, c]\\) and the state in the upper-left \\([i-1, c-wgt[i-1]]\\), so the entire \\(dp\\) table is traversed in order through two nested loops.

Based on the above analysis, we will next implement the brute force search, memoization, and dynamic programming solutions in order.

","path":["Chapter 14. Dynamic Programming","14.4   0-1 Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#1-method-1-brute-force-search","level":3,"title":"1.   Method 1: Brute Force Search","text":"

The search code includes the following elements.

  • Recursive parameters: state \\([i, c]\\).
  • Return value: solution to the subproblem \\(dp[i, c]\\).
  • Termination condition: when the item number is out of bounds \\(i = 0\\) or the remaining knapsack capacity is \\(0\\), terminate recursion and return value \\(0\\).
  • Pruning: if the weight of the current item exceeds the remaining knapsack capacity, only the option of not putting it in is available.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int:\n    \"\"\"0-1 knapsack: Brute-force search\"\"\"\n    # If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 or c == 0:\n        return 0\n    # If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c:\n        return knapsack_dfs(wgt, val, i - 1, c)\n    # Calculate the maximum value of not putting in and putting in item i\n    no = knapsack_dfs(wgt, val, i - 1, c)\n    yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]\n    # Return the larger value of the two options\n    return max(no, yes)\n
knapsack.cpp
/* 0-1 knapsack: Brute-force search */\nint knapsackDFS(vector<int> &wgt, vector<int> &val, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return max(no, yes);\n}\n
knapsack.java
/* 0-1 knapsack: Brute-force search */\nint knapsackDFS(int[] wgt, int[] val, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return Math.max(no, yes);\n}\n
knapsack.cs
/* 0-1 knapsack: Brute-force search */\nint KnapsackDFS(int[] weight, int[] val, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (weight[i - 1] > c) {\n        return KnapsackDFS(weight, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = KnapsackDFS(weight, val, i - 1, c);\n    int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return Math.Max(no, yes);\n}\n
knapsack.go
/* 0-1 knapsack: Brute-force search */\nfunc knapsackDFS(wgt, val []int, i, c int) int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i-1] > c {\n        return knapsackDFS(wgt, val, i-1, c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    no := knapsackDFS(wgt, val, i-1, c)\n    yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1]\n    // Return the larger value of the two options\n    return int(math.Max(float64(no), float64(yes)))\n}\n
knapsack.swift
/* 0-1 knapsack: Brute-force search */\nfunc knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c {\n        return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c)\n    let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1]\n    // Return the larger value of the two options\n    return max(no, yes)\n}\n
knapsack.js
/* 0-1 knapsack: Brute-force search */\nfunction knapsackDFS(wgt, val, i, c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    const no = knapsackDFS(wgt, val, i - 1, c);\n    const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return Math.max(no, yes);\n}\n
knapsack.ts
/* 0-1 knapsack: Brute-force search */\nfunction knapsackDFS(\n    wgt: Array<number>,\n    val: Array<number>,\n    i: number,\n    c: number\n): number {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    const no = knapsackDFS(wgt, val, i - 1, c);\n    const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return Math.max(no, yes);\n}\n
knapsack.dart
/* 0-1 knapsack: Brute-force search */\nint knapsackDFS(List<int> wgt, List<int> val, int i, int c) {\n  // If all items have been selected or knapsack has no remaining capacity, return value 0\n  if (i == 0 || c == 0) {\n    return 0;\n  }\n  // If exceeds knapsack capacity, can only choose not to put it in\n  if (wgt[i - 1] > c) {\n    return knapsackDFS(wgt, val, i - 1, c);\n  }\n  // Calculate the maximum value of not putting in and putting in item i\n  int no = knapsackDFS(wgt, val, i - 1, c);\n  int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n  // Return the larger value of the two options\n  return max(no, yes);\n}\n
knapsack.rs
/* 0-1 knapsack: Brute-force search */\nfn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c as i32 {\n        return knapsack_dfs(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    let no = knapsack_dfs(wgt, val, i - 1, c);\n    let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1];\n    // Return the larger value of the two options\n    std::cmp::max(no, yes)\n}\n
knapsack.c
/* 0-1 knapsack: Brute-force search */\nint knapsackDFS(int wgt[], int val[], int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return myMax(no, yes);\n}\n
knapsack.kt
/* 0-1 knapsack: Brute-force search */\nfun knapsackDFS(\n    wgt: IntArray,\n    _val: IntArray,\n    i: Int,\n    c: Int\n): Int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, _val, i - 1, c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    val no = knapsackDFS(wgt, _val, i - 1, c)\n    val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1]\n    // Return the larger value of the two options\n    return max(no, yes)\n}\n
knapsack.rb
### 0-1 knapsack: brute force search ###\ndef knapsack_dfs(wgt, val, i, c)\n  # If all items have been selected or knapsack has no remaining capacity, return value 0\n  return 0 if i == 0 || c == 0\n  # If exceeds knapsack capacity, can only choose not to put it in\n  return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c\n  # Calculate the maximum value of not putting in and putting in item i\n  no = knapsack_dfs(wgt, val, i - 1, c)\n  yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]\n  # Return the larger value of the two options\n  [no, yes].max\nend\n

As shown in Figure 14-18, since each item generates two search branches of not selecting and selecting, the time complexity is \\(O(2^n)\\).

Observing the recursion tree, it is easy to see overlapping subproblems, such as \\(dp[1, 10]\\). When there are many items, large knapsack capacity, and especially many items with the same weight, the number of overlapping subproblems will increase significantly.

Figure 14-18   Brute force search recursion tree for 0-1 knapsack problem

","path":["Chapter 14. Dynamic Programming","14.4   0-1 Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#2-method-2-memoization","level":3,"title":"2.   Method 2: Memoization","text":"

To ensure that overlapping subproblems are only computed once, we use a memo list mem to record the solutions to subproblems, where mem[i][c] corresponds to \\(dp[i, c]\\).

After introducing memoization, the time complexity depends on the number of subproblems, which is \\(O(n \\times cap)\\). The implementation code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
def knapsack_dfs_mem(\n    wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int\n) -> int:\n    \"\"\"0-1 knapsack: Memoization search\"\"\"\n    # If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 or c == 0:\n        return 0\n    # If there's a record, return it directly\n    if mem[i][c] != -1:\n        return mem[i][c]\n    # If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c:\n        return knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n    # Calculate the maximum value of not putting in and putting in item i\n    no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n    yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]\n    # Record and return the larger value of the two options\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n
knapsack.cpp
/* 0-1 knapsack: Memoization search */\nint knapsackDFSMem(vector<int> &wgt, vector<int> &val, vector<vector<int>> &mem, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = max(no, yes);\n    return mem[i][c];\n}\n
knapsack.java
/* 0-1 knapsack: Memoization search */\nint knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
knapsack.cs
/* 0-1 knapsack: Memoization search */\nint KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (weight[i - 1] > c) {\n        return KnapsackDFSMem(weight, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = KnapsackDFSMem(weight, val, mem, i - 1, c);\n    int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = Math.Max(no, yes);\n    return mem[i][c];\n}\n
knapsack.go
/* 0-1 knapsack: Memoization search */\nfunc knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If there's a record, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c]\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i-1] > c {\n        return knapsackDFSMem(wgt, val, mem, i-1, c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    no := knapsackDFSMem(wgt, val, mem, i-1, c)\n    yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1]\n    // Return the larger value of the two options\n    mem[i][c] = int(math.Max(float64(no), float64(yes)))\n    return mem[i][c]\n}\n
knapsack.swift
/* 0-1 knapsack: Memoization search */\nfunc knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If there's a record, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c]\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c {\n        return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c)\n    let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1]\n    // Record and return the larger value of the two options\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n}\n
knapsack.js
/* 0-1 knapsack: Memoization search */\nfunction knapsackDFSMem(wgt, val, mem, i, c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] !== -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    const no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    const yes =\n        knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
knapsack.ts
/* 0-1 knapsack: Memoization search */\nfunction knapsackDFSMem(\n    wgt: Array<number>,\n    val: Array<number>,\n    mem: Array<Array<number>>,\n    i: number,\n    c: number\n): number {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] !== -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    const no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    const yes =\n        knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
knapsack.dart
/* 0-1 knapsack: Memoization search */\nint knapsackDFSMem(\n  List<int> wgt,\n  List<int> val,\n  List<List<int>> mem,\n  int i,\n  int c,\n) {\n  // If all items have been selected or knapsack has no remaining capacity, return value 0\n  if (i == 0 || c == 0) {\n    return 0;\n  }\n  // If there's a record, return it directly\n  if (mem[i][c] != -1) {\n    return mem[i][c];\n  }\n  // If exceeds knapsack capacity, can only choose not to put it in\n  if (wgt[i - 1] > c) {\n    return knapsackDFSMem(wgt, val, mem, i - 1, c);\n  }\n  // Calculate the maximum value of not putting in and putting in item i\n  int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n  int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n  // Record and return the larger value of the two options\n  mem[i][c] = max(no, yes);\n  return mem[i][c];\n}\n
knapsack.rs
/* 0-1 knapsack: Memoization search */\nfn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec<Vec<i32>>, i: usize, c: usize) -> i32 {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c as i32 {\n        return knapsack_dfs_mem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c);\n    let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = std::cmp::max(no, yes);\n    mem[i][c]\n}\n
knapsack.c
/* 0-1 knapsack: Memoization search */\nint knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = myMax(no, yes);\n    return mem[i][c];\n}\n
knapsack.kt
/* 0-1 knapsack: Memoization search */\nfun knapsackDFSMem(\n    wgt: IntArray,\n    _val: IntArray,\n    mem: Array<IntArray>,\n    i: Int,\n    c: Int\n): Int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c]\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, _val, mem, i - 1, c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    val no = knapsackDFSMem(wgt, _val, mem, i - 1, c)\n    val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1]\n    // Record and return the larger value of the two options\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n}\n
knapsack.rb
### 0-1 knapsack: memoization search ###\ndef knapsack_dfs_mem(wgt, val, mem, i, c)\n  # If all items have been selected or knapsack has no remaining capacity, return value 0\n  return 0 if i == 0 || c == 0\n  # If there's a record, return it directly\n  return mem[i][c] if mem[i][c] != -1\n  # If exceeds knapsack capacity, can only choose not to put it in\n  return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c\n  # Calculate the maximum value of not putting in and putting in item i\n  no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n  yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]\n  # Record and return the larger value of the two options\n  mem[i][c] = [no, yes].max\nend\n

Figure 14-19 shows the search branches pruned in memoization.

Figure 14-19   Memoization recursion tree for 0-1 knapsack problem

","path":["Chapter 14. Dynamic Programming","14.4   0-1 Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#3-method-3-dynamic-programming","level":3,"title":"3.   Method 3: Dynamic Programming","text":"

Dynamic programming is essentially the process of filling the \\(dp\\) table during state transitions. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"0-1 knapsack: Dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize dp table\n    dp = [[0] * (cap + 1) for _ in range(n + 1)]\n    # State transition\n    for i in range(1, n + 1):\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            else:\n                # The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])\n    return dp[n][cap]\n
knapsack.cpp
/* 0-1 knapsack: Dynamic programming */\nint knapsackDP(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize dp table\n    vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0));\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
knapsack.java
/* 0-1 knapsack: Dynamic programming */\nint knapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize dp table\n    int[][] dp = new int[n + 1][cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
knapsack.cs
/* 0-1 knapsack: Dynamic programming */\nint KnapsackDP(int[] weight, int[] val, int cap) {\n    int n = weight.Length;\n    // Initialize dp table\n    int[,] dp = new int[n + 1, cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (weight[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i, c] = dp[i - 1, c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]);\n            }\n        }\n    }\n    return dp[n, cap];\n}\n
knapsack.go
/* 0-1 knapsack: Dynamic programming */\nfunc knapsackDP(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, cap+1)\n    }\n    // State transition\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i-1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
knapsack.swift
/* 0-1 knapsack: Dynamic programming */\nfunc knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize dp table\n    var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1)\n    // State transition\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
knapsack.js
/* 0-1 knapsack: Dynamic programming */\nfunction knapsackDP(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array(n + 1)\n        .fill(0)\n        .map(() => Array(cap + 1).fill(0));\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
knapsack.ts
/* 0-1 knapsack: Dynamic programming */\nfunction knapsackDP(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
knapsack.dart
/* 0-1 knapsack: Dynamic programming */\nint knapsackDP(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0));\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If exceeds knapsack capacity, don't select item i\n        dp[i][c] = dp[i - 1][c];\n      } else {\n        // The larger value between not selecting and selecting item i\n        dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[n][cap];\n}\n
knapsack.rs
/* 0-1 knapsack: Dynamic programming */\nfn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize dp table\n    let mut dp = vec![vec![0; cap + 1]; n + 1];\n    // State transition\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = std::cmp::max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1],\n                );\n            }\n        }\n    }\n    dp[n][cap]\n}\n
knapsack.c
/* 0-1 knapsack: Dynamic programming */\nint knapsackDP(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(cap + 1, sizeof(int));\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[n][cap];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
knapsack.kt
/* 0-1 knapsack: Dynamic programming */\nfun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize dp table\n    val dp = Array(n + 1) { IntArray(cap + 1) }\n    // State transition\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
knapsack.rb
### 0-1 knapsack: dynamic programming ###\ndef knapsack_dp(wgt, val, cap)\n  n = wgt.length\n  # Initialize dp table\n  dp = Array.new(n + 1) { Array.new(cap + 1, 0) }\n  # State transition\n  for i in 1...(n + 1)\n    for c in 1...(cap + 1)\n      if wgt[i - 1] > c\n        # If exceeds knapsack capacity, don't select item i\n        dp[i][c] = dp[i - 1][c]\n      else\n        # The larger value between not selecting and selecting item i\n        dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[n][cap]\nend\n

As shown in Figure 14-20, both time complexity and space complexity are determined by the size of the array dp, which is \\(O(n \\times cap)\\).

<1><2><3><4><5><6><7><8><9><10><11><12><13><14>

Figure 14-20   Dynamic programming process for 0-1 knapsack problem

","path":["Chapter 14. Dynamic Programming","14.4   0-1 Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#4-space-optimization","level":3,"title":"4.   Space Optimization","text":"

Since each state is only related to the state in the row above it, we can use two arrays rolling forward to reduce the space complexity from \\(O(n^2)\\) to \\(O(n)\\).

Further thinking, can we achieve space optimization using just one array? Observing, we can see that each state is transferred from the cell directly above or the cell in the upper-left. If there is only one array, when we start traversing row \\(i\\), that array still stores the state of row \\(i-1\\).

  • If using forward traversal, then when traversing to \\(dp[i, j]\\), the values in the upper-left \\(dp[i-1, 1]\\) ~ \\(dp[i-1, j-1]\\) may have already been overwritten, thus preventing correct state transition.
  • If using reverse traversal, there will be no overwriting issue, and state transition can proceed correctly.

Figure 14-21 shows the transition process from row \\(i = 1\\) to row \\(i = 2\\) using a single array. Please consider the difference between forward and reverse traversal.

<1><2><3><4><5><6>

Figure 14-21   Space-optimized dynamic programming process for 0-1 knapsack

In the code implementation, we simply need to delete the first dimension \\(i\\) of the array dp and change the inner loop to reverse traversal:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"0-1 knapsack: Space-optimized dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize dp table\n    dp = [0] * (cap + 1)\n    # State transition\n    for i in range(1, n + 1):\n        # Traverse in reverse order\n        for c in range(cap, 0, -1):\n            if wgt[i - 1] > c:\n                # If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c]\n            else:\n                # The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n    return dp[cap]\n
knapsack.cpp
/* 0-1 knapsack: Space-optimized dynamic programming */\nint knapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize dp table\n    vector<int> dp(cap + 1, 0);\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
knapsack.java
/* 0-1 knapsack: Space-optimized dynamic programming */\nint knapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize dp table\n    int[] dp = new int[cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
knapsack.cs
/* 0-1 knapsack: Space-optimized dynamic programming */\nint KnapsackDPComp(int[] weight, int[] val, int cap) {\n    int n = weight.Length;\n    // Initialize dp table\n    int[] dp = new int[cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (int c = cap; c > 0; c--) {\n            if (weight[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
knapsack.go
/* 0-1 knapsack: Space-optimized dynamic programming */\nfunc knapsackDPComp(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize dp table\n    dp := make([]int, cap+1)\n    // State transition\n    for i := 1; i <= n; i++ {\n        // Traverse in reverse order\n        for c := cap; c >= 1; c-- {\n            if wgt[i-1] <= c {\n                // The larger value between not selecting and selecting item i\n                dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[cap]\n}\n
knapsack.swift
/* 0-1 knapsack: Space-optimized dynamic programming */\nfunc knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize dp table\n    var dp = Array(repeating: 0, count: cap + 1)\n    // State transition\n    for i in 1 ... n {\n        // Traverse in reverse order\n        for c in (1 ... cap).reversed() {\n            if wgt[i - 1] <= c {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
knapsack.js
/* 0-1 knapsack: Space-optimized dynamic programming */\nfunction knapsackDPComp(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array(cap + 1).fill(0);\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (let c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
knapsack.ts
/* 0-1 knapsack: Space-optimized dynamic programming */\nfunction knapsackDPComp(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array(cap + 1).fill(0);\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (let c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
knapsack.dart
/* 0-1 knapsack: Space-optimized dynamic programming */\nint knapsackDPComp(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize dp table\n  List<int> dp = List.filled(cap + 1, 0);\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    // Traverse in reverse order\n    for (int c = cap; c >= 1; c--) {\n      if (wgt[i - 1] <= c) {\n        // The larger value between not selecting and selecting item i\n        dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[cap];\n}\n
knapsack.rs
/* 0-1 knapsack: Space-optimized dynamic programming */\nfn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize dp table\n    let mut dp = vec![0; cap + 1];\n    // State transition\n    for i in 1..=n {\n        // Traverse in reverse order\n        for c in (1..=cap).rev() {\n            if wgt[i - 1] <= c as i32 {\n                // The larger value between not selecting and selecting item i\n                dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    dp[cap]\n}\n
knapsack.c
/* 0-1 knapsack: Space-optimized dynamic programming */\nint knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize dp table\n    int *dp = calloc(cap + 1, sizeof(int));\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[cap];\n    // Free memory\n    free(dp);\n    return res;\n}\n
knapsack.kt
/* 0-1 knapsack: Space-optimized dynamic programming */\nfun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize dp table\n    val dp = IntArray(cap + 1)\n    // State transition\n    for (i in 1..n) {\n        // Traverse in reverse order\n        for (c in cap downTo 1) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
knapsack.rb
### 0-1 knapsack: space-optimized DP ###\ndef knapsack_dp_comp(wgt, val, cap)\n  n = wgt.length\n  # Initialize dp table\n  dp = Array.new(cap + 1, 0)\n  # State transition\n  for i in 1...(n + 1)\n    # Traverse in reverse order\n    for c in cap.downto(1)\n      if wgt[i - 1] > c\n        # If exceeds knapsack capacity, don't select item i\n        dp[c] = dp[c]\n      else\n        # The larger value between not selecting and selecting item i\n        dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[cap]\nend\n
","path":["Chapter 14. Dynamic Programming","14.4   0-1 Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/summary/","level":1,"title":"14.7   Summary","text":"","path":["Chapter 14. Dynamic Programming","14.7   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Dynamic programming decomposes problems and avoids redundant computation by storing the solutions to subproblems, thereby significantly improving computational efficiency.
  • Without considering time constraints, all dynamic programming problems can be solved using backtracking (brute force search), but the recursion tree contains a large number of overlapping subproblems, resulting in extremely low efficiency. By introducing a memo list, we can store the solutions to all computed subproblems, ensuring that overlapping subproblems are only computed once.
  • Memoization is a top-down recursive solution, while the corresponding dynamic programming is a bottom-up iterative solution, similar to \"filling in a table\". Since the current state only depends on certain local states, we can eliminate one dimension of the \\(dp\\) table to reduce space complexity.
  • Subproblem decomposition is a general algorithmic approach, with different properties in divide and conquer, dynamic programming, and backtracking.
  • Dynamic programming problems have three major characteristics: overlapping subproblems, optimal substructure, and no aftereffects.
  • If the optimal solution to the original problem can be constructed from the optimal solutions to the subproblems, then it has optimal substructure.
  • No aftereffects means that for a given state, its future development is only related to that state and has nothing to do with all past states. Many combinatorial optimization problems do not have no aftereffects and cannot be quickly solved using dynamic programming.

Knapsack problem

  • The knapsack problem is one of the most typical dynamic programming problems, with variants such as the 0-1 knapsack, unbounded knapsack, and multiple knapsack.
  • The state definition for the 0-1 knapsack is the maximum value among the first \\(i\\) items in a knapsack of capacity \\(c\\). Based on the two decisions of not putting an item in the knapsack and putting it in, the optimal substructure can be identified and the state transition equation constructed. In space optimization, since each state depends on the state directly above and to the upper-left, the list needs to be traversed in reverse order to avoid overwriting the upper-left state.
  • The unbounded knapsack problem has no limit on the selection quantity of each type of item, so the state transition for choosing to put in an item differs from the 0-1 knapsack problem. Since the state depends on the state directly above and directly to the left, space optimization should use forward traversal.
  • The coin change problem is a variant of the unbounded knapsack problem. It changes from seeking the \"maximum\" value to seeking the \"minimum\" number of coins, so \\(\\max()\\) in the state transition equation should be changed to \\(\\min()\\). It changes from seeking \"not exceeding\" the knapsack capacity to seeking \"exactly\" making up the target amount, so \\(amt + 1\\) is used to represent the invalid solution of \"unable to make up the target amount\".
  • Coin change problem II changes from seeking the \"minimum number of coins\" to seeking the \"number of coin combinations\", so the state transition equation correspondingly changes from \\(\\min()\\) to a summation operator.

Edit distance problem

  • Edit distance (Levenshtein distance) is used to measure the similarity between two strings, defined as the minimum number of edit steps from one string to another, with edit operations including insert, delete, and replace.
  • The state definition for the edit distance problem is the minimum number of edit steps required to change the first \\(i\\) characters of \\(s\\) into the first \\(j\\) characters of \\(t\\). When \\(s[i] \\ne t[j]\\), there are three decisions: insert, delete, replace, each with corresponding remaining subproblems. From this, the optimal substructure can be identified and the state transition equation constructed. When \\(s[i] = t[j]\\), no edit is required for the current character.
  • In edit distance, the state depends on the state directly above, directly to the left, and to the upper-left, so after space optimization, neither forward nor reverse traversal can correctly perform state transitions. For this reason, we use a variable to temporarily store the upper-left state, thus transforming to a situation equivalent to the unbounded knapsack problem, allowing for forward traversal after space optimization.
","path":["Chapter 14. Dynamic Programming","14.7   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/","level":1,"title":"14.5   Unbounded Knapsack Problem","text":"

In this section, we first solve another common knapsack problem: the unbounded knapsack, and then explore a special case of it: the coin change problem.

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1451-unbounded-knapsack-problem","level":2,"title":"14.5.1   Unbounded Knapsack Problem","text":"

Question

Given \\(n\\) items, where the weight of the \\(i\\)-th item is \\(wgt[i-1]\\) and its value is \\(val[i-1]\\), and a knapsack with capacity \\(cap\\). Each item can be selected multiple times. What is the maximum value that can be placed in the knapsack within the capacity limit? An example is shown in Figure 14-22.

Figure 14-22   Example data for unbounded knapsack problem

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach","level":3,"title":"1.   Dynamic Programming Approach","text":"

The unbounded knapsack problem is very similar to the 0-1 knapsack problem, differing only in that there is no limit on the number of times an item can be selected.

  • In the 0-1 knapsack problem, there is only one of each type of item, so after placing item \\(i\\) in the knapsack, we can only choose from the first \\(i-1\\) items.
  • In the unbounded knapsack problem, the quantity of each type of item is unlimited, so after placing item \\(i\\) in the knapsack, we can still choose from the first \\(i\\) items.

Under the rules of the unbounded knapsack problem, the changes in state \\([i, c]\\) are divided into two cases.

  • Not putting item \\(i\\): Same as the 0-1 knapsack problem, transfer to \\([i-1, c]\\).
  • Putting item \\(i\\): Different from the 0-1 knapsack problem, transfer to \\([i, c-wgt[i-1]]\\).

Thus, the state transition equation becomes:

\\[ dp[i, c] = \\max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) \\]","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"

Comparing the code for the two problems, there is one change in state transition from \\(i-1\\) to \\(i\\), with everything else identical:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby unbounded_knapsack.py
def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Unbounded knapsack: Dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize dp table\n    dp = [[0] * (cap + 1) for _ in range(n + 1)]\n    # State transition\n    for i in range(1, n + 1):\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            else:\n                # The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])\n    return dp[n][cap]\n
unbounded_knapsack.cpp
/* Unbounded knapsack: Dynamic programming */\nint unboundedKnapsackDP(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize dp table\n    vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0));\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
unbounded_knapsack.java
/* Unbounded knapsack: Dynamic programming */\nint unboundedKnapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize dp table\n    int[][] dp = new int[n + 1][cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
unbounded_knapsack.cs
/* Unbounded knapsack: Dynamic programming */\nint UnboundedKnapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.Length;\n    // Initialize dp table\n    int[,] dp = new int[n + 1, cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i, c] = dp[i - 1, c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n, cap];\n}\n
unbounded_knapsack.go
/* Unbounded knapsack: Dynamic programming */\nfunc unboundedKnapsackDP(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, cap+1)\n    }\n    // State transition\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i-1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
unbounded_knapsack.swift
/* Unbounded knapsack: Dynamic programming */\nfunc unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize dp table\n    var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1)\n    // State transition\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
unbounded_knapsack.js
/* Unbounded knapsack: Dynamic programming */\nfunction unboundedKnapsackDP(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
unbounded_knapsack.ts
/* Unbounded knapsack: Dynamic programming */\nfunction unboundedKnapsackDP(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
unbounded_knapsack.dart
/* Unbounded knapsack: Dynamic programming */\nint unboundedKnapsackDP(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0));\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If exceeds knapsack capacity, don't select item i\n        dp[i][c] = dp[i - 1][c];\n      } else {\n        // The larger value between not selecting and selecting item i\n        dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[n][cap];\n}\n
unbounded_knapsack.rs
/* Unbounded knapsack: Dynamic programming */\nfn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize dp table\n    let mut dp = vec![vec![0; cap + 1]; n + 1];\n    // State transition\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
unbounded_knapsack.c
/* Unbounded knapsack: Dynamic programming */\nint unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(cap + 1, sizeof(int));\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[n][cap];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
unbounded_knapsack.kt
/* Unbounded knapsack: Dynamic programming */\nfun unboundedKnapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize dp table\n    val dp = Array(n + 1) { IntArray(cap + 1) }\n    // State transition\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
unbounded_knapsack.rb
### Unbounded knapsack: dynamic programming ###\ndef unbounded_knapsack_dp(wgt, val, cap)\n  n = wgt.length\n  # Initialize dp table\n  dp = Array.new(n + 1) { Array.new(cap + 1, 0) }\n  # State transition\n  for i in 1...(n + 1)\n    for c in 1...(cap + 1)\n      if wgt[i - 1] > c\n        # If exceeds knapsack capacity, don't select item i\n        dp[i][c] = dp[i - 1][c]\n      else\n        # The larger value between not selecting and selecting item i\n        dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[n][cap]\nend\n
","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization","level":3,"title":"3.   Space Optimization","text":"

Since the current state is transferred from states on the left and above, after space optimization, each row in the \\(dp\\) table should be traversed in forward order.

This traversal order is exactly opposite to the 0-1 knapsack. Please refer to Figure 14-23 to understand the difference between the two.

<1><2><3><4><5><6>

Figure 14-23   Space-optimized dynamic programming process for unbounded knapsack problem

The code implementation is relatively simple, just delete the first dimension of the array dp:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby unbounded_knapsack.py
def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Unbounded knapsack: Space-optimized dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize dp table\n    dp = [0] * (cap + 1)\n    # State transition\n    for i in range(1, n + 1):\n        # Traverse in forward order\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c]\n            else:\n                # The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n    return dp[cap]\n
unbounded_knapsack.cpp
/* Unbounded knapsack: Space-optimized dynamic programming */\nint unboundedKnapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize dp table\n    vector<int> dp(cap + 1, 0);\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
unbounded_knapsack.java
/* Unbounded knapsack: Space-optimized dynamic programming */\nint unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize dp table\n    int[] dp = new int[cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
unbounded_knapsack.cs
/* Unbounded knapsack: Space-optimized dynamic programming */\nint UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.Length;\n    // Initialize dp table\n    int[] dp = new int[cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
unbounded_knapsack.go
/* Unbounded knapsack: Space-optimized dynamic programming */\nfunc unboundedKnapsackDPComp(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize dp table\n    dp := make([]int, cap+1)\n    // State transition\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[cap]\n}\n
unbounded_knapsack.swift
/* Unbounded knapsack: Space-optimized dynamic programming */\nfunc unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize dp table\n    var dp = Array(repeating: 0, count: cap + 1)\n    // State transition\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
unbounded_knapsack.js
/* Unbounded knapsack: Space-optimized dynamic programming */\nfunction unboundedKnapsackDPComp(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array.from({ length: cap + 1 }, () => 0);\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
unbounded_knapsack.ts
/* Unbounded knapsack: Space-optimized dynamic programming */\nfunction unboundedKnapsackDPComp(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array.from({ length: cap + 1 }, () => 0);\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
unbounded_knapsack.dart
/* Unbounded knapsack: Space-optimized dynamic programming */\nint unboundedKnapsackDPComp(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize dp table\n  List<int> dp = List.filled(cap + 1, 0);\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If exceeds knapsack capacity, don't select item i\n        dp[c] = dp[c];\n      } else {\n        // The larger value between not selecting and selecting item i\n        dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[cap];\n}\n
unbounded_knapsack.rs
/* Unbounded knapsack: Space-optimized dynamic programming */\nfn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize dp table\n    let mut dp = vec![0; cap + 1];\n    // State transition\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    dp[cap]\n}\n
unbounded_knapsack.c
/* Unbounded knapsack: Space-optimized dynamic programming */\nint unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize dp table\n    int *dp = calloc(cap + 1, sizeof(int));\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[cap];\n    // Free memory\n    free(dp);\n    return res;\n}\n
unbounded_knapsack.kt
/* Unbounded knapsack: Space-optimized dynamic programming */\nfun unboundedKnapsackDPComp(\n    wgt: IntArray,\n    _val: IntArray,\n    cap: Int\n): Int {\n    val n = wgt.size\n    // Initialize dp table\n    val dp = IntArray(cap + 1)\n    // State transition\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
unbounded_knapsack.rb
### Unbounded knapsack: space-optimized DP ###\ndef unbounded_knapsack_dp_comp(wgt, val, cap)\n  n = wgt.length\n  # Initialize dp table\n  dp = Array.new(cap + 1, 0)\n  # State transition\n  for i in 1...(n + 1)\n    # Traverse in forward order\n    for c in 1...(cap + 1)\n      if wgt[i -1] > c\n        # If exceeds knapsack capacity, don't select item i\n        dp[c] = dp[c]\n      else\n        # The larger value between not selecting and selecting item i\n        dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[cap]\nend\n
","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1452-coin-change-problem","level":2,"title":"14.5.2   Coin Change Problem","text":"

The knapsack problem represents a large class of dynamic programming problems and has many variants, such as the coin change problem.

Question

Given \\(n\\) types of coins, where the denomination of the \\(i\\)-th type of coin is \\(coins[i - 1]\\), and the target amount is \\(amt\\). Each type of coin can be selected multiple times. What is the minimum number of coins needed to make up the target amount? If it is impossible to make up the target amount, return \\(-1\\). An example is shown in Figure 14-24.

Figure 14-24   Example data for coin change problem

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach_1","level":3,"title":"1.   Dynamic Programming Approach","text":"

The coin change problem can be viewed as a special case of the unbounded knapsack problem, with the following connections and differences.

  • The two problems can be converted to each other: \"item\" corresponds to \"coin\", \"item weight\" corresponds to \"coin denomination\", and \"knapsack capacity\" corresponds to \"target amount\".
  • The optimization goals are opposite: the unbounded knapsack problem aims to maximize item value, while the coin change problem aims to minimize the number of coins.
  • The unbounded knapsack problem seeks solutions \"not exceeding\" the knapsack capacity, while the coin change problem seeks solutions that \"exactly\" make up the target amount.

Step 1: Think about the decisions in each round, define the state, and thus obtain the \\(dp\\) table

State \\([i, a]\\) corresponds to the subproblem: the minimum number of coins among the first \\(i\\) types of coins that can make up amount \\(a\\), denoted as \\(dp[i, a]\\).

The two-dimensional \\(dp\\) table has size \\((n+1) \\times (amt+1)\\).

Step 2: Identify the optimal substructure, and then derive the state transition equation

This problem differs from the unbounded knapsack problem in the following two aspects regarding the state transition equation.

  • This problem seeks the minimum value, so the operator \\(\\max()\\) needs to be changed to \\(\\min()\\).
  • The optimization target is the number of coins rather than item value, so when a coin is selected, simply execute \\(+1\\).
\\[ dp[i, a] = \\min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) \\]

Step 3: Determine boundary conditions and state transition order

When the target amount is \\(0\\), the minimum number of coins needed to make it up is \\(0\\), so all \\(dp[i, 0]\\) in the first column equal \\(0\\).

When there are no coins, it is impossible to make up any amount \\(> 0\\), which is an invalid solution. To enable the \\(\\min()\\) function in the state transition equation to identify and filter out invalid solutions, we consider using \\(+ \\infty\\) to represent them, i.e., set all \\(dp[0, a]\\) in the first row to \\(+ \\infty\\).

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation_1","level":3,"title":"2.   Code Implementation","text":"

Most programming languages do not provide a \\(+ \\infty\\) variable, and can only use the maximum value of integer type int as a substitute. However, this can lead to large number overflow: the \\(+ 1\\) operation in the state transition equation may cause overflow.

For this reason, we use the number \\(amt + 1\\) to represent invalid solutions, because the maximum number of coins needed to make up \\(amt\\) is at most \\(amt\\). Before returning, check whether \\(dp[n, amt]\\) equals \\(amt + 1\\); if so, return \\(-1\\), indicating that the target amount cannot be made up. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change.py
def coin_change_dp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: Dynamic programming\"\"\"\n    n = len(coins)\n    MAX = amt + 1\n    # Initialize dp table\n    dp = [[0] * (amt + 1) for _ in range(n + 1)]\n    # State transition: first row and first column\n    for a in range(1, amt + 1):\n        dp[0][a] = MAX\n    # State transition: rest of the rows and columns\n    for i in range(1, n + 1):\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            else:\n                # The smaller value between not selecting and selecting coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n    return dp[n][amt] if dp[n][amt] != MAX else -1\n
coin_change.cpp
/* Coin change: Dynamic programming */\nint coinChangeDP(vector<int> &coins, int amt) {\n    int n = coins.size();\n    int MAX = amt + 1;\n    // Initialize dp table\n    vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));\n    // State transition: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
coin_change.java
/* Coin change: Dynamic programming */\nint coinChangeDP(int[] coins, int amt) {\n    int n = coins.length;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int[][] dp = new int[n + 1][amt + 1];\n    // State transition: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
coin_change.cs
/* Coin change: Dynamic programming */\nint CoinChangeDP(int[] coins, int amt) {\n    int n = coins.Length;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int[,] dp = new int[n + 1, amt + 1];\n    // State transition: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0, a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i, a] = dp[i - 1, a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n, amt] != MAX ? dp[n, amt] : -1;\n}\n
coin_change.go
/* Coin change: Dynamic programming */\nfunc coinChangeDP(coins []int, amt int) int {\n    n := len(coins)\n    max := amt + 1\n    // Initialize dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, amt+1)\n    }\n    // State transition: first row and first column\n    for a := 1; a <= amt; a++ {\n        dp[0][a] = max\n    }\n    // State transition: rest of the rows and columns\n    for i := 1; i <= n; i++ {\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i-1][a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1)))\n            }\n        }\n    }\n    if dp[n][amt] != max {\n        return dp[n][amt]\n    }\n    return -1\n}\n
coin_change.swift
/* Coin change: Dynamic programming */\nfunc coinChangeDP(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    let MAX = amt + 1\n    // Initialize dp table\n    var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1)\n    // State transition: first row and first column\n    for a in 1 ... amt {\n        dp[0][a] = MAX\n    }\n    // State transition: rest of the rows and columns\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1\n}\n
coin_change.js
/* Coin change: Dynamic programming */\nfunction coinChangeDP(coins, amt) {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // State transition: first row and first column\n    for (let a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] !== MAX ? dp[n][amt] : -1;\n}\n
coin_change.ts
/* Coin change: Dynamic programming */\nfunction coinChangeDP(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // State transition: first row and first column\n    for (let a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] !== MAX ? dp[n][amt] : -1;\n}\n
coin_change.dart
/* Coin change: Dynamic programming */\nint coinChangeDP(List<int> coins, int amt) {\n  int n = coins.length;\n  int MAX = amt + 1;\n  // Initialize dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0));\n  // State transition: first row and first column\n  for (int a = 1; a <= amt; a++) {\n    dp[0][a] = MAX;\n  }\n  // State transition: rest of the rows and columns\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If exceeds target amount, don't select coin i\n        dp[i][a] = dp[i - 1][a];\n      } else {\n        // The smaller value between not selecting and selecting coin i\n        dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n      }\n    }\n  }\n  return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
coin_change.rs
/* Coin change: Dynamic programming */\nfn coin_change_dp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    let max = amt + 1;\n    // Initialize dp table\n    let mut dp = vec![vec![0; amt + 1]; n + 1];\n    // State transition: first row and first column\n    for a in 1..=amt {\n        dp[0][a] = max;\n    }\n    // State transition: rest of the rows and columns\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1);\n            }\n        }\n    }\n    if dp[n][amt] != max {\n        return dp[n][amt] as i32;\n    } else {\n        -1\n    }\n}\n
coin_change.c
/* Coin change: Dynamic programming */\nint coinChangeDP(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(amt + 1, sizeof(int));\n    }\n    // State transition: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    int res = dp[n][amt] != MAX ? dp[n][amt] : -1;\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
coin_change.kt
/* Coin change: Dynamic programming */\nfun coinChangeDP(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    val MAX = amt + 1\n    // Initialize dp table\n    val dp = Array(n + 1) { IntArray(amt + 1) }\n    // State transition: first row and first column\n    for (a in 1..amt) {\n        dp[0][a] = MAX\n    }\n    // State transition: rest of the rows and columns\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return if (dp[n][amt] != MAX) dp[n][amt] else -1\n}\n
coin_change.rb
### Coin change: dynamic programming ###\ndef coin_change_dp(coins, amt)\n  n = coins.length\n  _MAX = amt + 1\n  # Initialize dp table\n  dp = Array.new(n + 1) { Array.new(amt + 1, 0) }\n  # State transition: first row and first column\n  (1...(amt + 1)).each { |a| dp[0][a] = _MAX }\n  # State transition: rest of the rows and columns\n  for i in 1...(n + 1)\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If exceeds target amount, don't select coin i\n        dp[i][a] = dp[i - 1][a]\n      else\n        # The smaller value between not selecting and selecting coin i\n        dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min\n      end\n    end\n  end\n  dp[n][amt] != _MAX ? dp[n][amt] : -1\nend\n

Figure 14-25 shows the dynamic programming process for coin change, which is very similar to the unbounded knapsack problem.

<1><2><3><4><5><6><7><8><9><10><11><12><13><14><15>

Figure 14-25   Dynamic programming process for coin change problem

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization_1","level":3,"title":"3.   Space Optimization","text":"

The space optimization for the coin change problem is handled in the same way as the unbounded knapsack problem:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change.py
def coin_change_dp_comp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: Space-optimized dynamic programming\"\"\"\n    n = len(coins)\n    MAX = amt + 1\n    # Initialize dp table\n    dp = [MAX] * (amt + 1)\n    dp[0] = 0\n    # State transition\n    for i in range(1, n + 1):\n        # Traverse in forward order\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            else:\n                # The smaller value between not selecting and selecting coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n    return dp[amt] if dp[amt] != MAX else -1\n
coin_change.cpp
/* Coin change: Space-optimized dynamic programming */\nint coinChangeDPComp(vector<int> &coins, int amt) {\n    int n = coins.size();\n    int MAX = amt + 1;\n    // Initialize dp table\n    vector<int> dp(amt + 1, MAX);\n    dp[0] = 0;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
coin_change.java
/* Coin change: Space-optimized dynamic programming */\nint coinChangeDPComp(int[] coins, int amt) {\n    int n = coins.length;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int[] dp = new int[amt + 1];\n    Arrays.fill(dp, MAX);\n    dp[0] = 0;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
coin_change.cs
/* Coin change: Space-optimized dynamic programming */\nint CoinChangeDPComp(int[] coins, int amt) {\n    int n = coins.Length;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int[] dp = new int[amt + 1];\n    Array.Fill(dp, MAX);\n    dp[0] = 0;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
coin_change.go
/* Coin change: Dynamic programming */\nfunc coinChangeDPComp(coins []int, amt int) int {\n    n := len(coins)\n    max := amt + 1\n    // Initialize dp table\n    dp := make([]int, amt+1)\n    for i := 1; i <= amt; i++ {\n        dp[i] = max\n    }\n    // State transition\n    for i := 1; i <= n; i++ {\n        // Traverse in forward order\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1)))\n            }\n        }\n    }\n    if dp[amt] != max {\n        return dp[amt]\n    }\n    return -1\n}\n
coin_change.swift
/* Coin change: Space-optimized dynamic programming */\nfunc coinChangeDPComp(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    let MAX = amt + 1\n    // Initialize dp table\n    var dp = Array(repeating: MAX, count: amt + 1)\n    dp[0] = 0\n    // State transition\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1\n}\n
coin_change.js
/* Coin change: Space-optimized dynamic programming */\nfunction coinChangeDPComp(coins, amt) {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize dp table\n    const dp = Array.from({ length: amt + 1 }, () => MAX);\n    dp[0] = 0;\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] !== MAX ? dp[amt] : -1;\n}\n
coin_change.ts
/* Coin change: Space-optimized dynamic programming */\nfunction coinChangeDPComp(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize dp table\n    const dp = Array.from({ length: amt + 1 }, () => MAX);\n    dp[0] = 0;\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] !== MAX ? dp[amt] : -1;\n}\n
coin_change.dart
/* Coin change: Space-optimized dynamic programming */\nint coinChangeDPComp(List<int> coins, int amt) {\n  int n = coins.length;\n  int MAX = amt + 1;\n  // Initialize dp table\n  List<int> dp = List.filled(amt + 1, MAX);\n  dp[0] = 0;\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If exceeds target amount, don't select coin i\n        dp[a] = dp[a];\n      } else {\n        // The smaller value between not selecting and selecting coin i\n        dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);\n      }\n    }\n  }\n  return dp[amt] != MAX ? dp[amt] : -1;\n}\n
coin_change.rs
/* Coin change: Space-optimized dynamic programming */\nfn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    let max = amt + 1;\n    // Initialize dp table\n    let mut dp = vec![0; amt + 1];\n    dp.fill(max);\n    dp[0] = 0;\n    // State transition\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1);\n            }\n        }\n    }\n    if dp[amt] != max {\n        return dp[amt] as i32;\n    } else {\n        -1\n    }\n}\n
coin_change.c
/* Coin change: Space-optimized dynamic programming */\nint coinChangeDPComp(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int *dp = malloc((amt + 1) * sizeof(int));\n    for (int j = 1; j <= amt; j++) {\n        dp[j] = MAX;\n    } \n    dp[0] = 0;\n\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    int res = dp[amt] != MAX ? dp[amt] : -1;\n    // Free memory\n    free(dp);\n    return res;\n}\n
coin_change.kt
/* Coin change: Space-optimized dynamic programming */\nfun coinChangeDPComp(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    val MAX = amt + 1\n    // Initialize dp table\n    val dp = IntArray(amt + 1)\n    dp.fill(MAX)\n    dp[0] = 0\n    // State transition\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return if (dp[amt] != MAX) dp[amt] else -1\n}\n
coin_change.rb
### Coin change: space-optimized DP ###\ndef coin_change_dp_comp(coins, amt)\n  n = coins.length\n  _MAX = amt + 1\n  # Initialize dp table\n  dp = Array.new(amt + 1, _MAX)\n  dp[0] = 0\n  # State transition\n  for i in 1...(n + 1)\n    # Traverse in forward order\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If exceeds target amount, don't select coin i\n        dp[a] = dp[a]\n      else\n        # The smaller value between not selecting and selecting coin i\n        dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min\n      end\n    end\n  end\n  dp[amt] != _MAX ? dp[amt] : -1\nend\n
","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1453-coin-change-problem-ii","level":2,"title":"14.5.3   Coin Change Problem Ii","text":"

Question

Given \\(n\\) types of coins, where the denomination of the \\(i\\)-th type of coin is \\(coins[i - 1]\\), and the target amount is \\(amt\\). Each type of coin can be selected multiple times. What is the number of coin combinations that can make up the target amount? An example is shown in Figure 14-26.

Figure 14-26   Example data for coin change problem II

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach_2","level":3,"title":"1.   Dynamic Programming Approach","text":"

Compared to the previous problem, this problem's goal is to find the number of combinations, so the subproblem becomes: the number of combinations among the first \\(i\\) types of coins that can make up amount \\(a\\). The \\(dp\\) table remains a two-dimensional matrix of size \\((n+1) \\times (amt + 1)\\).

The number of combinations for the current state equals the sum of the combinations from not selecting the current coin and selecting the current coin. The state transition equation is:

\\[ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] \\]

When the target amount is \\(0\\), no coins need to be selected to make up the target amount, so all \\(dp[i, 0]\\) in the first column should be initialized to \\(1\\). When there are no coins, it is impossible to make up any amount \\(>0\\), so all \\(dp[0, a]\\) in the first row equal \\(0\\).

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation_2","level":3,"title":"2.   Code Implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_ii.py
def coin_change_ii_dp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change II: Dynamic programming\"\"\"\n    n = len(coins)\n    # Initialize dp table\n    dp = [[0] * (amt + 1) for _ in range(n + 1)]\n    # Initialize first column\n    for i in range(n + 1):\n        dp[i][0] = 1\n    # State transition\n    for i in range(1, n + 1):\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            else:\n                # Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n    return dp[n][amt]\n
coin_change_ii.cpp
/* Coin change II: Dynamic programming */\nint coinChangeIIDP(vector<int> &coins, int amt) {\n    int n = coins.size();\n    // Initialize dp table\n    vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));\n    // Initialize first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
coin_change_ii.java
/* Coin change II: Dynamic programming */\nint coinChangeIIDP(int[] coins, int amt) {\n    int n = coins.length;\n    // Initialize dp table\n    int[][] dp = new int[n + 1][amt + 1];\n    // Initialize first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
coin_change_ii.cs
/* Coin change II: Dynamic programming */\nint CoinChangeIIDP(int[] coins, int amt) {\n    int n = coins.Length;\n    // Initialize dp table\n    int[,] dp = new int[n + 1, amt + 1];\n    // Initialize first column\n    for (int i = 0; i <= n; i++) {\n        dp[i, 0] = 1;\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i, a] = dp[i - 1, a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n, amt];\n}\n
coin_change_ii.go
/* Coin change II: Dynamic programming */\nfunc coinChangeIIDP(coins []int, amt int) int {\n    n := len(coins)\n    // Initialize dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, amt+1)\n    }\n    // Initialize first column\n    for i := 0; i <= n; i++ {\n        dp[i][0] = 1\n    }\n    // State transition: rest of the rows and columns\n    for i := 1; i <= n; i++ {\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i-1][a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
coin_change_ii.swift
/* Coin change II: Dynamic programming */\nfunc coinChangeIIDP(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    // Initialize dp table\n    var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1)\n    // Initialize first column\n    for i in 0 ... n {\n        dp[i][0] = 1\n    }\n    // State transition\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
coin_change_ii.js
/* Coin change II: Dynamic programming */\nfunction coinChangeIIDP(coins, amt) {\n    const n = coins.length;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // Initialize first column\n    for (let i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
coin_change_ii.ts
/* Coin change II: Dynamic programming */\nfunction coinChangeIIDP(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // Initialize first column\n    for (let i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
coin_change_ii.dart
/* Coin change II: Dynamic programming */\nint coinChangeIIDP(List<int> coins, int amt) {\n  int n = coins.length;\n  // Initialize dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0));\n  // Initialize first column\n  for (int i = 0; i <= n; i++) {\n    dp[i][0] = 1;\n  }\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If exceeds target amount, don't select coin i\n        dp[i][a] = dp[i - 1][a];\n      } else {\n        // Sum of the two options: not selecting and selecting coin i\n        dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n      }\n    }\n  }\n  return dp[n][amt];\n}\n
coin_change_ii.rs
/* Coin change II: Dynamic programming */\nfn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    // Initialize dp table\n    let mut dp = vec![vec![0; amt + 1]; n + 1];\n    // Initialize first column\n    for i in 0..=n {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize];\n            }\n        }\n    }\n    dp[n][amt]\n}\n
coin_change_ii.c
/* Coin change II: Dynamic programming */\nint coinChangeIIDP(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    // Initialize dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(amt + 1, sizeof(int));\n    }\n    // Initialize first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    int res = dp[n][amt];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
coin_change_ii.kt
/* Coin change II: Dynamic programming */\nfun coinChangeIIDP(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    // Initialize dp table\n    val dp = Array(n + 1) { IntArray(amt + 1) }\n    // Initialize first column\n    for (i in 0..n) {\n        dp[i][0] = 1\n    }\n    // State transition\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
coin_change_ii.rb
### Coin change II: dynamic programming ###\ndef coin_change_ii_dp(coins, amt)\n  n = coins.length\n  # Initialize dp table\n  dp = Array.new(n + 1) { Array.new(amt + 1, 0) }\n  # Initialize first column\n  (0...(n + 1)).each { |i| dp[i][0] = 1 }\n  # State transition\n  for i in 1...(n + 1)\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If exceeds target amount, don't select coin i\n        dp[i][a] = dp[i - 1][a]\n      else\n        # Sum of the two options: not selecting and selecting coin i\n        dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n      end\n    end\n  end\n  dp[n][amt]\nend\n
","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization_2","level":3,"title":"3.   Space Optimization","text":"

The space optimization is handled in the same way, just delete the coin dimension:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_ii.py
def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change II: Space-optimized dynamic programming\"\"\"\n    n = len(coins)\n    # Initialize dp table\n    dp = [0] * (amt + 1)\n    dp[0] = 1\n    # State transition\n    for i in range(1, n + 1):\n        # Traverse in forward order\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            else:\n                # Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n    return dp[amt]\n
coin_change_ii.cpp
/* Coin change II: Space-optimized dynamic programming */\nint coinChangeIIDPComp(vector<int> &coins, int amt) {\n    int n = coins.size();\n    // Initialize dp table\n    vector<int> dp(amt + 1, 0);\n    dp[0] = 1;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
coin_change_ii.java
/* Coin change II: Space-optimized dynamic programming */\nint coinChangeIIDPComp(int[] coins, int amt) {\n    int n = coins.length;\n    // Initialize dp table\n    int[] dp = new int[amt + 1];\n    dp[0] = 1;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
coin_change_ii.cs
/* Coin change II: Space-optimized dynamic programming */\nint CoinChangeIIDPComp(int[] coins, int amt) {\n    int n = coins.Length;\n    // Initialize dp table\n    int[] dp = new int[amt + 1];\n    dp[0] = 1;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
coin_change_ii.go
/* Coin change II: Space-optimized dynamic programming */\nfunc coinChangeIIDPComp(coins []int, amt int) int {\n    n := len(coins)\n    // Initialize dp table\n    dp := make([]int, amt+1)\n    dp[0] = 1\n    // State transition\n    for i := 1; i <= n; i++ {\n        // Traverse in forward order\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a-coins[i-1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
coin_change_ii.swift
/* Coin change II: Space-optimized dynamic programming */\nfunc coinChangeIIDPComp(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    // Initialize dp table\n    var dp = Array(repeating: 0, count: amt + 1)\n    dp[0] = 1\n    // State transition\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
coin_change_ii.js
/* Coin change II: Space-optimized dynamic programming */\nfunction coinChangeIIDPComp(coins, amt) {\n    const n = coins.length;\n    // Initialize dp table\n    const dp = Array.from({ length: amt + 1 }, () => 0);\n    dp[0] = 1;\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
coin_change_ii.ts
/* Coin change II: Space-optimized dynamic programming */\nfunction coinChangeIIDPComp(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    // Initialize dp table\n    const dp = Array.from({ length: amt + 1 }, () => 0);\n    dp[0] = 1;\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
coin_change_ii.dart
/* Coin change II: Space-optimized dynamic programming */\nint coinChangeIIDPComp(List<int> coins, int amt) {\n  int n = coins.length;\n  // Initialize dp table\n  List<int> dp = List.filled(amt + 1, 0);\n  dp[0] = 1;\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If exceeds target amount, don't select coin i\n        dp[a] = dp[a];\n      } else {\n        // Sum of the two options: not selecting and selecting coin i\n        dp[a] = dp[a] + dp[a - coins[i - 1]];\n      }\n    }\n  }\n  return dp[amt];\n}\n
coin_change_ii.rs
/* Coin change II: Space-optimized dynamic programming */\nfn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    // Initialize dp table\n    let mut dp = vec![0; amt + 1];\n    dp[0] = 1;\n    // State transition\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1] as usize];\n            }\n        }\n    }\n    dp[amt]\n}\n
coin_change_ii.c
/* Coin change II: Space-optimized dynamic programming */\nint coinChangeIIDPComp(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    // Initialize dp table\n    int *dp = calloc(amt + 1, sizeof(int));\n    dp[0] = 1;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    int res = dp[amt];\n    // Free memory\n    free(dp);\n    return res;\n}\n
coin_change_ii.kt
/* Coin change II: Space-optimized dynamic programming */\nfun coinChangeIIDPComp(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    // Initialize dp table\n    val dp = IntArray(amt + 1)\n    dp[0] = 1\n    // State transition\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
coin_change_ii.rb
### Coin change II: space-optimized DP ###\ndef coin_change_ii_dp_comp(coins, amt)\n  n = coins.length\n  # Initialize dp table\n  dp = Array.new(amt + 1, 0)\n  dp[0] = 1\n  # State transition\n  for i in 1...(n + 1)\n    # Traverse in forward order\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If exceeds target amount, don't select coin i\n        dp[a] = dp[a]\n      else\n        # Sum of the two options: not selecting and selecting coin i\n        dp[a] = dp[a] + dp[a - coins[i - 1]]\n      end\n    end\n  end\n  dp[amt]\nend\n
","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_graph/","level":1,"title":"Chapter 9.   Graph","text":"

Abstract

In the journey of life, we are like nodes, connected by countless invisible edges.

Each encounter and parting leaves a unique mark on this vast network graph.

","path":["Chapter 9. Graph","Chapter 9.   Graph"],"tags":[]},{"location":"chapter_graph/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 9.1   Graph
  • 9.2   Basic Operations on Graphs
  • 9.3   Graph Traversal
  • 9.4   Summary
","path":["Chapter 9. Graph","Chapter 9.   Graph"],"tags":[]},{"location":"chapter_graph/graph/","level":1,"title":"9.1   Graph","text":"

A graph is a nonlinear data structure consisting of vertices and edges. We can abstractly represent a graph \\(G\\) as a set of vertices \\(V\\) and a set of edges \\(E\\). The following example shows a graph containing 5 vertices and 7 edges.

\\[ \\begin{aligned} V & = \\{ 1, 2, 3, 4, 5 \\} \\newline E & = \\{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \\} \\newline G & = \\{ V, E \\} \\newline \\end{aligned} \\]

If we view vertices as nodes and edges as references (pointers) connecting the nodes, we can see graphs as a data structure extended from linked lists. As shown in Figure 9-1, compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) have a higher degree of freedom and are therefore more complex.

Figure 9-1   Relationships among linked lists, trees, and graphs

","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph/#911-common-types-and-terminology-of-graphs","level":2,"title":"9.1.1   Common Types and Terminology of Graphs","text":"

Graphs can be divided into undirected graphs and directed graphs based on whether edges have direction, as shown in Figure 9-2.

  • In undirected graphs, edges represent a \"bidirectional\" connection between two vertices, such as the \"friend relationship\" on WeChat or QQ.
  • In directed graphs, edges have directionality, meaning edges \\(A \\rightarrow B\\) and \\(A \\leftarrow B\\) are independent of each other, such as the \"follow\" and \"be followed\" relationships on Weibo or TikTok.

Figure 9-2   Directed and undirected graphs

Graphs can be divided into connected graphs and disconnected graphs based on whether all vertices are connected, as shown in Figure 9-3.

  • For connected graphs, starting from any vertex, all other vertices can be reached.
  • For disconnected graphs, starting from a certain vertex, at least one vertex cannot be reached.

Figure 9-3   Connected and disconnected graphs

We can also add a \"weight\" variable to edges, resulting in weighted graphs as shown in Figure 9-4. For example, in mobile games like \"Honor of Kings\", the system calculates the \"intimacy\" between players based on their shared game time, and such intimacy networks can be represented using weighted graphs.

Figure 9-4   Weighted and unweighted graphs

Graph data structures include the following commonly used terms.

  • Adjacency: When two vertices are connected by an edge, these two vertices are said to be \"adjacent\". In Figure 9-4, the adjacent vertices of vertex 1 are vertices 2, 3, and 5.
  • Path: The sequence of edges from vertex A to vertex B is called a \"path\" from A to B. In Figure 9-4, the edge sequence 1-5-2-4 is a path from vertex 1 to vertex 4.
  • Degree: The number of edges a vertex has. For directed graphs, in-degree indicates how many edges point to the vertex, and out-degree indicates how many edges point out from the vertex.
","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph/#912-representation-of-graphs","level":2,"title":"9.1.2   Representation of Graphs","text":"

Common representations of graphs include \"adjacency matrices\" and \"adjacency lists\". The following uses undirected graphs as examples.

","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph/#1-adjacency-matrix","level":3,"title":"1.   Adjacency Matrix","text":"

Given a graph with \\(n\\) vertices, an adjacency matrix uses an \\(n \\times n\\) matrix to represent the graph, where each row (column) represents a vertex, and matrix elements represent edges, using \\(1\\) or \\(0\\) to indicate whether an edge exists between two vertices.

As shown in Figure 9-5, let the adjacency matrix be \\(M\\) and the vertex list be \\(V\\). Then matrix element \\(M[i, j] = 1\\) indicates that an edge exists between vertex \\(V[i]\\) and vertex \\(V[j]\\), whereas \\(M[i, j] = 0\\) indicates no edge between the two vertices.

Figure 9-5   Adjacency matrix representation of a graph

Adjacency matrices have the following properties.

  • In simple graphs, vertices cannot connect to themselves, so the elements on the main diagonal of the adjacency matrix are meaningless.
  • For undirected graphs, edges in both directions are equivalent, so the adjacency matrix is symmetric about the main diagonal.
  • Replacing the elements of the adjacency matrix from \\(1\\) and \\(0\\) to weights allows representation of weighted graphs.

When using adjacency matrices to represent graphs, we can directly access matrix elements to obtain edges, resulting in highly efficient addition, deletion, lookup, and modification operations, all with a time complexity of \\(O(1)\\). However, the space complexity of the matrix is \\(O(n^2)\\), which consumes significant memory.

","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph/#2-adjacency-list","level":3,"title":"2.   Adjacency List","text":"

An adjacency list uses \\(n\\) linked lists to represent a graph, with linked list nodes representing vertices. The \\(i\\)-th linked list corresponds to vertex \\(i\\) and stores all adjacent vertices of that vertex (vertices connected to that vertex). Figure 9-6 shows an example of a graph stored using an adjacency list.

Figure 9-6   Adjacency list representation of a graph

Adjacency lists only store edges that actually exist, and the total number of edges is typically much less than \\(n^2\\), making them more space-efficient. However, finding edges in an adjacency list requires traversing the linked list, so its time efficiency is inferior to that of adjacency matrices.

Observing Figure 9-6, the structure of adjacency lists is very similar to \"chaining\" in hash tables, so we can adopt similar methods to optimize efficiency. For example, when linked lists are long, they can be converted to AVL trees or red-black trees, thereby optimizing time efficiency from \\(O(n)\\) to \\(O(\\log n)\\); linked lists can also be converted to hash tables, thereby reducing time complexity to \\(O(1)\\).

","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph/#913-common-applications-of-graphs","level":2,"title":"9.1.3   Common Applications of Graphs","text":"

As shown in Table 9-1, many real-world systems can be modeled using graphs, and corresponding problems can be reduced to graph computation problems.

Table 9-1   Common graphs in real life

Vertices Edges Graph Computation Problem Social network Users Friend relationships Potential friend recommendation Subway lines Stations Connectivity between stations Shortest route recommendation Solar system Celestial bodies Gravitational forces between celestial bodies Planetary orbit calculation","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph_operations/","level":1,"title":"9.2   Basic Operations on Graphs","text":"

Basic operations on graphs can be divided into operations on \"edges\" and operations on \"vertices\". Under the two representation methods of \"adjacency matrix\" and \"adjacency list\", the implementation methods differ.

","path":["Chapter 9. Graph","9.2   Basic Operations on Graphs"],"tags":[]},{"location":"chapter_graph/graph_operations/#921-implementation-based-on-adjacency-matrix","level":2,"title":"9.2.1   Implementation Based on Adjacency Matrix","text":"

Given an undirected graph with \\(n\\) vertices, the various operations are implemented as shown in Figure 9-7.

  • Adding or removing an edge: Directly modify the specified edge in the adjacency matrix, using \\(O(1)\\) time. Since it is an undirected graph, both directions of the edge need to be updated simultaneously.
  • Adding a vertex: Add a row and a column at the end of the adjacency matrix and fill them all with \\(0\\)s, using \\(O(n)\\) time.
  • Removing a vertex: Delete a row and a column in the adjacency matrix. The worst case occurs when removing the first row and column, requiring \\((n-1)^2\\) elements to be \"moved up and to the left\", thus using \\(O(n^2)\\) time.
  • Initialization: Pass in \\(n\\) vertices, initialize a vertex list vertices of length \\(n\\), using \\(O(n)\\) time; initialize an adjacency matrix adjMat of size \\(n \\times n\\), using \\(O(n^2)\\) time.
Initialize adjacency matrixAdd an edgeRemove an edgeAdd a vertexRemove a vertex

Figure 9-7   Initialization, adding and removing edges, adding and removing vertices in adjacency matrix

The following is the implementation code for graphs represented using an adjacency matrix:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_adjacency_matrix.py
class GraphAdjMat:\n    \"\"\"Undirected graph class based on adjacency matrix\"\"\"\n\n    def __init__(self, vertices: list[int], edges: list[list[int]]):\n        \"\"\"Constructor\"\"\"\n        # Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n        self.vertices: list[int] = []\n        # Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n        self.adj_mat: list[list[int]] = []\n        # Add vertices\n        for val in vertices:\n            self.add_vertex(val)\n        # Add edges\n        # Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for e in edges:\n            self.add_edge(e[0], e[1])\n\n    def size(self) -> int:\n        \"\"\"Get the number of vertices\"\"\"\n        return len(self.vertices)\n\n    def add_vertex(self, val: int):\n        \"\"\"Add vertex\"\"\"\n        n = self.size()\n        # Add the value of the new vertex to the vertex list\n        self.vertices.append(val)\n        # Add a row to the adjacency matrix\n        new_row = [0] * n\n        self.adj_mat.append(new_row)\n        # Add a column to the adjacency matrix\n        for row in self.adj_mat:\n            row.append(0)\n\n    def remove_vertex(self, index: int):\n        \"\"\"Remove vertex\"\"\"\n        if index >= self.size():\n            raise IndexError()\n        # Remove the vertex at index from the vertex list\n        self.vertices.pop(index)\n        # Remove the row at index from the adjacency matrix\n        self.adj_mat.pop(index)\n        # Remove the column at index from the adjacency matrix\n        for row in self.adj_mat:\n            row.pop(index)\n\n    def add_edge(self, i: int, j: int):\n        \"\"\"Add edge\"\"\"\n        # Parameters i, j correspond to the vertices element indices\n        # Handle index out of bounds and equality\n        if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:\n            raise IndexError()\n        # In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        self.adj_mat[i][j] = 1\n        self.adj_mat[j][i] = 1\n\n    def remove_edge(self, i: int, j: int):\n        \"\"\"Remove edge\"\"\"\n        # Parameters i, j correspond to the vertices element indices\n        # Handle index out of bounds and equality\n        if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:\n            raise IndexError()\n        self.adj_mat[i][j] = 0\n        self.adj_mat[j][i] = 0\n\n    def print(self):\n        \"\"\"Print adjacency matrix\"\"\"\n        print(\"Vertex list =\", self.vertices)\n        print(\"Adjacency matrix =\")\n        print_matrix(self.adj_mat)\n
graph_adjacency_matrix.cpp
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    vector<int> vertices;       // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    vector<vector<int>> adjMat; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n  public:\n    /* Constructor */\n    GraphAdjMat(const vector<int> &vertices, const vector<vector<int>> &edges) {\n        // Add vertex\n        for (int val : vertices) {\n            addVertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for (const vector<int> &edge : edges) {\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int size() const {\n        return vertices.size();\n    }\n\n    /* Add vertex */\n    void addVertex(int val) {\n        int n = size();\n        // Add the value of the new vertex to the vertex list\n        vertices.push_back(val);\n        // Add a row to the adjacency matrix\n        adjMat.emplace_back(vector<int>(n, 0));\n        // Add a column to the adjacency matrix\n        for (vector<int> &row : adjMat) {\n            row.push_back(0);\n        }\n    }\n\n    /* Remove vertex */\n    void removeVertex(int index) {\n        if (index >= size()) {\n            throw out_of_range(\"Vertex does not exist\");\n        }\n        // Remove the vertex at index from the vertex list\n        vertices.erase(vertices.begin() + index);\n        // Remove the row at index from the adjacency matrix\n        adjMat.erase(adjMat.begin() + index);\n        // Remove the column at index from the adjacency matrix\n        for (vector<int> &row : adjMat) {\n            row.erase(row.begin() + index);\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    void addEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n            throw out_of_range(\"Vertex does not exist\");\n        }\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        adjMat[i][j] = 1;\n        adjMat[j][i] = 1;\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    void removeEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n            throw out_of_range(\"Vertex does not exist\");\n        }\n        adjMat[i][j] = 0;\n        adjMat[j][i] = 0;\n    }\n\n    /* Print adjacency matrix */\n    void print() {\n        cout << \"Vertex list = \";\n        printVector(vertices);\n        cout << \"Adjacency matrix =\" << endl;\n        printVectorMatrix(adjMat);\n    }\n};\n
graph_adjacency_matrix.java
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    List<Integer> vertices; // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    List<List<Integer>> adjMat; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    public GraphAdjMat(int[] vertices, int[][] edges) {\n        this.vertices = new ArrayList<>();\n        this.adjMat = new ArrayList<>();\n        // Add vertex\n        for (int val : vertices) {\n            addVertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for (int[] e : edges) {\n            addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    public int size() {\n        return vertices.size();\n    }\n\n    /* Add vertex */\n    public void addVertex(int val) {\n        int n = size();\n        // Add the value of the new vertex to the vertex list\n        vertices.add(val);\n        // Add a row to the adjacency matrix\n        List<Integer> newRow = new ArrayList<>(n);\n        for (int j = 0; j < n; j++) {\n            newRow.add(0);\n        }\n        adjMat.add(newRow);\n        // Add a column to the adjacency matrix\n        for (List<Integer> row : adjMat) {\n            row.add(0);\n        }\n    }\n\n    /* Remove vertex */\n    public void removeVertex(int index) {\n        if (index >= size())\n            throw new IndexOutOfBoundsException();\n        // Remove the vertex at index from the vertex list\n        vertices.remove(index);\n        // Remove the row at index from the adjacency matrix\n        adjMat.remove(index);\n        // Remove the column at index from the adjacency matrix\n        for (List<Integer> row : adjMat) {\n            row.remove(index);\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    public void addEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw new IndexOutOfBoundsException();\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        adjMat.get(i).set(j, 1);\n        adjMat.get(j).set(i, 1);\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    public void removeEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw new IndexOutOfBoundsException();\n        adjMat.get(i).set(j, 0);\n        adjMat.get(j).set(i, 0);\n    }\n\n    /* Print adjacency matrix */\n    public void print() {\n        System.out.print(\"Vertex list = \");\n        System.out.println(vertices);\n        System.out.println(\"Adjacency matrix =\");\n        PrintUtil.printMatrix(adjMat);\n    }\n}\n
graph_adjacency_matrix.cs
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    List<int> vertices;     // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    List<List<int>> adjMat; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    public GraphAdjMat(int[] vertices, int[][] edges) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add vertex\n        foreach (int val in vertices) {\n            AddVertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        foreach (int[] e in edges) {\n            AddEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int Size() {\n        return vertices.Count;\n    }\n\n    /* Add vertex */\n    public void AddVertex(int val) {\n        int n = Size();\n        // Add the value of the new vertex to the vertex list\n        vertices.Add(val);\n        // Add a row to the adjacency matrix\n        List<int> newRow = new(n);\n        for (int j = 0; j < n; j++) {\n            newRow.Add(0);\n        }\n        adjMat.Add(newRow);\n        // Add a column to the adjacency matrix\n        foreach (List<int> row in adjMat) {\n            row.Add(0);\n        }\n    }\n\n    /* Remove vertex */\n    public void RemoveVertex(int index) {\n        if (index >= Size())\n            throw new IndexOutOfRangeException();\n        // Remove the vertex at index from the vertex list\n        vertices.RemoveAt(index);\n        // Remove the row at index from the adjacency matrix\n        adjMat.RemoveAt(index);\n        // Remove the column at index from the adjacency matrix\n        foreach (List<int> row in adjMat) {\n            row.RemoveAt(index);\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    public void AddEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j)\n            throw new IndexOutOfRangeException();\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        adjMat[i][j] = 1;\n        adjMat[j][i] = 1;\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    public void RemoveEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j)\n            throw new IndexOutOfRangeException();\n        adjMat[i][j] = 0;\n        adjMat[j][i] = 0;\n    }\n\n    /* Print adjacency matrix */\n    public void Print() {\n        Console.Write(\"Vertex list = \");\n        PrintUtil.PrintList(vertices);\n        Console.WriteLine(\"Adjacency matrix =\");\n        PrintUtil.PrintMatrix(adjMat);\n    }\n}\n
graph_adjacency_matrix.go
/* Undirected graph class based on adjacency matrix */\ntype graphAdjMat struct {\n    // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    vertices []int\n    // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n    adjMat [][]int\n}\n\n/* Constructor */\nfunc newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat {\n    // Add vertex\n    n := len(vertices)\n    adjMat := make([][]int, n)\n    for i := range adjMat {\n        adjMat[i] = make([]int, n)\n    }\n    // Initialize graph\n    g := &graphAdjMat{\n        vertices: vertices,\n        adjMat:   adjMat,\n    }\n    // Add edge\n    // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n    for i := range edges {\n        g.addEdge(edges[i][0], edges[i][1])\n    }\n    return g\n}\n\n/* Get the number of vertices */\nfunc (g *graphAdjMat) size() int {\n    return len(g.vertices)\n}\n\n/* Add vertex */\nfunc (g *graphAdjMat) addVertex(val int) {\n    n := g.size()\n    // Add the value of the new vertex to the vertex list\n    g.vertices = append(g.vertices, val)\n    // Add a row to the adjacency matrix\n    newRow := make([]int, n)\n    g.adjMat = append(g.adjMat, newRow)\n    // Add a column to the adjacency matrix\n    for i := range g.adjMat {\n        g.adjMat[i] = append(g.adjMat[i], 0)\n    }\n}\n\n/* Remove vertex */\nfunc (g *graphAdjMat) removeVertex(index int) {\n    if index >= g.size() {\n        return\n    }\n    // Remove the vertex at index from the vertex list\n    g.vertices = append(g.vertices[:index], g.vertices[index+1:]...)\n    // Remove the row at index from the adjacency matrix\n    g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...)\n    // Remove the column at index from the adjacency matrix\n    for i := range g.adjMat {\n        g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...)\n    }\n}\n\n/* Add edge */\n// Parameters i, j correspond to the vertices element indices\nfunc (g *graphAdjMat) addEdge(i, j int) {\n    // Handle index out of bounds and equality\n    if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {\n        fmt.Errorf(\"%s\", \"Index Out Of Bounds Exception\")\n    }\n    // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n    g.adjMat[i][j] = 1\n    g.adjMat[j][i] = 1\n}\n\n/* Remove edge */\n// Parameters i, j correspond to the vertices element indices\nfunc (g *graphAdjMat) removeEdge(i, j int) {\n    // Handle index out of bounds and equality\n    if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {\n        fmt.Errorf(\"%s\", \"Index Out Of Bounds Exception\")\n    }\n    g.adjMat[i][j] = 0\n    g.adjMat[j][i] = 0\n}\n\n/* Print adjacency matrix */\nfunc (g *graphAdjMat) print() {\n    fmt.Printf(\"\\tVertex list = %v\\n\", g.vertices)\n    fmt.Printf(\"\\tAdjacency matrix = \\n\")\n    for i := range g.adjMat {\n        fmt.Printf(\"\\t\\t\\t%v\\n\", g.adjMat[i])\n    }\n}\n
graph_adjacency_matrix.swift
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    private var vertices: [Int] // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    private var adjMat: [[Int]] // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    init(vertices: [Int], edges: [[Int]]) {\n        self.vertices = []\n        adjMat = []\n        // Add vertex\n        for val in vertices {\n            addVertex(val: val)\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for e in edges {\n            addEdge(i: e[0], j: e[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    func size() -> Int {\n        vertices.count\n    }\n\n    /* Add vertex */\n    func addVertex(val: Int) {\n        let n = size()\n        // Add the value of the new vertex to the vertex list\n        vertices.append(val)\n        // Add a row to the adjacency matrix\n        let newRow = Array(repeating: 0, count: n)\n        adjMat.append(newRow)\n        // Add a column to the adjacency matrix\n        for i in adjMat.indices {\n            adjMat[i].append(0)\n        }\n    }\n\n    /* Remove vertex */\n    func removeVertex(index: Int) {\n        if index >= size() {\n            fatalError(\"Out of bounds\")\n        }\n        // Remove the vertex at index from the vertex list\n        vertices.remove(at: index)\n        // Remove the row at index from the adjacency matrix\n        adjMat.remove(at: index)\n        // Remove the column at index from the adjacency matrix\n        for i in adjMat.indices {\n            adjMat[i].remove(at: index)\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    func addEdge(i: Int, j: Int) {\n        // Handle index out of bounds and equality\n        if i < 0 || j < 0 || i >= size() || j >= size() || i == j {\n            fatalError(\"Out of bounds\")\n        }\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        adjMat[i][j] = 1\n        adjMat[j][i] = 1\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    func removeEdge(i: Int, j: Int) {\n        // Handle index out of bounds and equality\n        if i < 0 || j < 0 || i >= size() || j >= size() || i == j {\n            fatalError(\"Out of bounds\")\n        }\n        adjMat[i][j] = 0\n        adjMat[j][i] = 0\n    }\n\n    /* Print adjacency matrix */\n    func print() {\n        Swift.print(\"Vertex list = \", terminator: \"\")\n        Swift.print(vertices)\n        Swift.print(\"Adjacency matrix =\")\n        PrintUtil.printMatrix(matrix: adjMat)\n    }\n}\n
graph_adjacency_matrix.js
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    vertices; // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    adjMat; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    constructor(vertices, edges) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add vertex\n        for (const val of vertices) {\n            this.addVertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for (const e of edges) {\n            this.addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size() {\n        return this.vertices.length;\n    }\n\n    /* Add vertex */\n    addVertex(val) {\n        const n = this.size();\n        // Add the value of the new vertex to the vertex list\n        this.vertices.push(val);\n        // Add a row to the adjacency matrix\n        const newRow = [];\n        for (let j = 0; j < n; j++) {\n            newRow.push(0);\n        }\n        this.adjMat.push(newRow);\n        // Add a column to the adjacency matrix\n        for (const row of this.adjMat) {\n            row.push(0);\n        }\n    }\n\n    /* Remove vertex */\n    removeVertex(index) {\n        if (index >= this.size()) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        // Remove the vertex at index from the vertex list\n        this.vertices.splice(index, 1);\n\n        // Remove the row at index from the adjacency matrix\n        this.adjMat.splice(index, 1);\n        // Remove the column at index from the adjacency matrix\n        for (const row of this.adjMat) {\n            row.splice(index, 1);\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    addEdge(i, j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        // In undirected graph, adjacency matrix is symmetric about main diagonal, i.e., satisfies (i, j) === (j, i)\n        this.adjMat[i][j] = 1;\n        this.adjMat[j][i] = 1;\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    removeEdge(i, j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        this.adjMat[i][j] = 0;\n        this.adjMat[j][i] = 0;\n    }\n\n    /* Print adjacency matrix */\n    print() {\n        console.log('Vertex list = ', this.vertices);\n        console.log('Adjacency matrix =', this.adjMat);\n    }\n}\n
graph_adjacency_matrix.ts
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    vertices: number[]; // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    adjMat: number[][]; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    constructor(vertices: number[], edges: number[][]) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add vertex\n        for (const val of vertices) {\n            this.addVertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for (const e of edges) {\n            this.addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size(): number {\n        return this.vertices.length;\n    }\n\n    /* Add vertex */\n    addVertex(val: number): void {\n        const n: number = this.size();\n        // Add the value of the new vertex to the vertex list\n        this.vertices.push(val);\n        // Add a row to the adjacency matrix\n        const newRow: number[] = [];\n        for (let j: number = 0; j < n; j++) {\n            newRow.push(0);\n        }\n        this.adjMat.push(newRow);\n        // Add a column to the adjacency matrix\n        for (const row of this.adjMat) {\n            row.push(0);\n        }\n    }\n\n    /* Remove vertex */\n    removeVertex(index: number): void {\n        if (index >= this.size()) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        // Remove the vertex at index from the vertex list\n        this.vertices.splice(index, 1);\n\n        // Remove the row at index from the adjacency matrix\n        this.adjMat.splice(index, 1);\n        // Remove the column at index from the adjacency matrix\n        for (const row of this.adjMat) {\n            row.splice(index, 1);\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    addEdge(i: number, j: number): void {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        // In undirected graph, adjacency matrix is symmetric about main diagonal, i.e., satisfies (i, j) === (j, i)\n        this.adjMat[i][j] = 1;\n        this.adjMat[j][i] = 1;\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    removeEdge(i: number, j: number): void {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        this.adjMat[i][j] = 0;\n        this.adjMat[j][i] = 0;\n    }\n\n    /* Print adjacency matrix */\n    print(): void {\n        console.log('Vertex list = ', this.vertices);\n        console.log('Adjacency matrix =', this.adjMat);\n    }\n}\n
graph_adjacency_matrix.dart
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n  List<int> vertices = []; // Vertex elements, elements represent \"vertex values\", indices represent \"vertex indices\"\n  List<List<int>> adjMat = []; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n  /* Constructor */\n  GraphAdjMat(List<int> vertices, List<List<int>> edges) {\n    this.vertices = [];\n    this.adjMat = [];\n    // Add vertex\n    for (int val in vertices) {\n      addVertex(val);\n    }\n    // Add edge\n    // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n    for (List<int> e in edges) {\n      addEdge(e[0], e[1]);\n    }\n  }\n\n  /* Get the number of vertices */\n  int size() {\n    return vertices.length;\n  }\n\n  /* Add vertex */\n  void addVertex(int val) {\n    int n = size();\n    // Add the value of the new vertex to the vertex list\n    vertices.add(val);\n    // Add a row to the adjacency matrix\n    List<int> newRow = List.filled(n, 0, growable: true);\n    adjMat.add(newRow);\n    // Add a column to the adjacency matrix\n    for (List<int> row in adjMat) {\n      row.add(0);\n    }\n  }\n\n  /* Remove vertex */\n  void removeVertex(int index) {\n    if (index >= size()) {\n      throw IndexError;\n    }\n    // Remove the vertex at index from the vertex list\n    vertices.removeAt(index);\n    // Remove the row at index from the adjacency matrix\n    adjMat.removeAt(index);\n    // Remove the column at index from the adjacency matrix\n    for (List<int> row in adjMat) {\n      row.removeAt(index);\n    }\n  }\n\n  /* Add edge */\n  // Parameters i, j correspond to the vertices element indices\n  void addEdge(int i, int j) {\n    // Handle index out of bounds and equality\n    if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n      throw IndexError;\n    }\n    // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n    adjMat[i][j] = 1;\n    adjMat[j][i] = 1;\n  }\n\n  /* Remove edge */\n  // Parameters i, j correspond to the vertices element indices\n  void removeEdge(int i, int j) {\n    // Handle index out of bounds and equality\n    if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n      throw IndexError;\n    }\n    adjMat[i][j] = 0;\n    adjMat[j][i] = 0;\n  }\n\n  /* Print adjacency matrix */\n  void printAdjMat() {\n    print(\"Vertex list = $vertices\");\n    print(\"Adjacency matrix = \");\n    printMatrix(adjMat);\n  }\n}\n
graph_adjacency_matrix.rs
/* Undirected graph type based on adjacency matrix */\npub struct GraphAdjMat {\n    // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    pub vertices: Vec<i32>,\n    // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n    pub adj_mat: Vec<Vec<i32>>,\n}\n\nimpl GraphAdjMat {\n    /* Constructor */\n    pub fn new(vertices: Vec<i32>, edges: Vec<[usize; 2]>) -> Self {\n        let mut graph = GraphAdjMat {\n            vertices: vec![],\n            adj_mat: vec![],\n        };\n        // Add vertex\n        for val in vertices {\n            graph.add_vertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for edge in edges {\n            graph.add_edge(edge[0], edge[1])\n        }\n\n        graph\n    }\n\n    /* Get the number of vertices */\n    pub fn size(&self) -> usize {\n        self.vertices.len()\n    }\n\n    /* Add vertex */\n    pub fn add_vertex(&mut self, val: i32) {\n        let n = self.size();\n        // Add the value of the new vertex to the vertex list\n        self.vertices.push(val);\n        // Add a row to the adjacency matrix\n        self.adj_mat.push(vec![0; n]);\n        // Add a column to the adjacency matrix\n        for row in self.adj_mat.iter_mut() {\n            row.push(0);\n        }\n    }\n\n    /* Remove vertex */\n    pub fn remove_vertex(&mut self, index: usize) {\n        if index >= self.size() {\n            panic!(\"index error\")\n        }\n        // Remove the vertex at index from the vertex list\n        self.vertices.remove(index);\n        // Remove the row at index from the adjacency matrix\n        self.adj_mat.remove(index);\n        // Remove the column at index from the adjacency matrix\n        for row in self.adj_mat.iter_mut() {\n            row.remove(index);\n        }\n    }\n\n    /* Add edge */\n    pub fn add_edge(&mut self, i: usize, j: usize) {\n        // Parameters i, j correspond to the vertices element indices\n        // Handle index out of bounds and equality\n        if i >= self.size() || j >= self.size() || i == j {\n            panic!(\"index error\")\n        }\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        self.adj_mat[i][j] = 1;\n        self.adj_mat[j][i] = 1;\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    pub fn remove_edge(&mut self, i: usize, j: usize) {\n        // Parameters i, j correspond to the vertices element indices\n        // Handle index out of bounds and equality\n        if i >= self.size() || j >= self.size() || i == j {\n            panic!(\"index error\")\n        }\n        self.adj_mat[i][j] = 0;\n        self.adj_mat[j][i] = 0;\n    }\n\n    /* Print adjacency matrix */\n    pub fn print(&self) {\n        println!(\"Vertex list = {:?}\", self.vertices);\n        println!(\"Adjacency matrix =\");\n        println!(\"[\");\n        for row in &self.adj_mat {\n            println!(\"  {:?},\", row);\n        }\n        println!(\"]\")\n    }\n}\n
graph_adjacency_matrix.c
/* Undirected graph structure based on adjacency matrix */\ntypedef struct {\n    int vertices[MAX_SIZE];\n    int adjMat[MAX_SIZE][MAX_SIZE];\n    int size;\n} GraphAdjMat;\n\n/* Constructor */\nGraphAdjMat *newGraphAdjMat() {\n    GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat));\n    graph->size = 0;\n    for (int i = 0; i < MAX_SIZE; i++) {\n        for (int j = 0; j < MAX_SIZE; j++) {\n            graph->adjMat[i][j] = 0;\n        }\n    }\n    return graph;\n}\n\n/* Destructor */\nvoid delGraphAdjMat(GraphAdjMat *graph) {\n    free(graph);\n}\n\n/* Add vertex */\nvoid addVertex(GraphAdjMat *graph, int val) {\n    if (graph->size == MAX_SIZE) {\n        fprintf(stderr, \"Graph vertex count has reached maximum\\n\");\n        return;\n    }\n    // Add nth vertex and zero nth row and column\n    int n = graph->size;\n    graph->vertices[n] = val;\n    for (int i = 0; i <= n; i++) {\n        graph->adjMat[n][i] = graph->adjMat[i][n] = 0;\n    }\n    graph->size++;\n}\n\n/* Remove vertex */\nvoid removeVertex(GraphAdjMat *graph, int index) {\n    if (index < 0 || index >= graph->size) {\n        fprintf(stderr, \"Vertex index out of bounds\\n\");\n        return;\n    }\n    // Remove the vertex at index from the vertex list\n    for (int i = index; i < graph->size - 1; i++) {\n        graph->vertices[i] = graph->vertices[i + 1];\n    }\n    // Remove the row at index from the adjacency matrix\n    for (int i = index; i < graph->size - 1; i++) {\n        for (int j = 0; j < graph->size; j++) {\n            graph->adjMat[i][j] = graph->adjMat[i + 1][j];\n        }\n    }\n    // Remove the column at index from the adjacency matrix\n    for (int i = 0; i < graph->size; i++) {\n        for (int j = index; j < graph->size - 1; j++) {\n            graph->adjMat[i][j] = graph->adjMat[i][j + 1];\n        }\n    }\n    graph->size--;\n}\n\n/* Add edge */\n// Parameters i, j correspond to the vertices element indices\nvoid addEdge(GraphAdjMat *graph, int i, int j) {\n    if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) {\n        fprintf(stderr, \"Edge index out of bounds or equal\\n\");\n        return;\n    }\n    graph->adjMat[i][j] = 1;\n    graph->adjMat[j][i] = 1;\n}\n\n/* Remove edge */\n// Parameters i, j correspond to the vertices element indices\nvoid removeEdge(GraphAdjMat *graph, int i, int j) {\n    if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) {\n        fprintf(stderr, \"Edge index out of bounds or equal\\n\");\n        return;\n    }\n    graph->adjMat[i][j] = 0;\n    graph->adjMat[j][i] = 0;\n}\n\n/* Print adjacency matrix */\nvoid printGraphAdjMat(GraphAdjMat *graph) {\n    printf(\"Vertex list = \");\n    printArray(graph->vertices, graph->size);\n    printf(\"Adjacency matrix =\\n\");\n    for (int i = 0; i < graph->size; i++) {\n        printArray(graph->adjMat[i], graph->size);\n    }\n}\n
graph_adjacency_matrix.kt
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat(vertices: IntArray, edges: Array<IntArray>) {\n    val vertices = mutableListOf<Int>() // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    val adjMat = mutableListOf<MutableList<Int>>() // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    init {\n        // Add vertex\n        for (vertex in vertices) {\n            addVertex(vertex)\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for (edge in edges) {\n            addEdge(edge[0], edge[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    fun size(): Int {\n        return vertices.size\n    }\n\n    /* Add vertex */\n    fun addVertex(_val: Int) {\n        val n = size()\n        // Add the value of the new vertex to the vertex list\n        vertices.add(_val)\n        // Add a row to the adjacency matrix\n        val newRow = mutableListOf<Int>()\n        for (j in 0..<n) {\n            newRow.add(0)\n        }\n        adjMat.add(newRow)\n        // Add a column to the adjacency matrix\n        for (row in adjMat) {\n            row.add(0)\n        }\n    }\n\n    /* Remove vertex */\n    fun removeVertex(index: Int) {\n        if (index >= size())\n            throw IndexOutOfBoundsException()\n        // Remove the vertex at index from the vertex list\n        vertices.removeAt(index)\n        // Remove the row at index from the adjacency matrix\n        adjMat.removeAt(index)\n        // Remove the column at index from the adjacency matrix\n        for (row in adjMat) {\n            row.removeAt(index)\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    fun addEdge(i: Int, j: Int) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw IndexOutOfBoundsException()\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        adjMat[i][j] = 1\n        adjMat[j][i] = 1\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    fun removeEdge(i: Int, j: Int) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw IndexOutOfBoundsException()\n        adjMat[i][j] = 0\n        adjMat[j][i] = 0\n    }\n\n    /* Print adjacency matrix */\n    fun print() {\n        print(\"Vertex list = \")\n        println(vertices)\n        println(\"Adjacency matrix =\")\n        printMatrix(adjMat)\n    }\n}\n
graph_adjacency_matrix.rb
### Undirected graph class based on adjacency matrix ###\nclass GraphAdjMat\n  def initialize(vertices, edges)\n    ### Constructor ###\n    # Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    @vertices = []\n    # Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n    @adj_mat = []\n    # Add vertex\n    vertices.each { |val| add_vertex(val) }\n    # Add edge\n    # Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n    edges.each { |e| add_edge(e[0], e[1]) }\n  end\n\n  ### Get number of vertices ###\n  def size\n    @vertices.length\n  end\n\n  ### Add vertex ###\n  def add_vertex(val)\n    n = size\n    # Add the value of the new vertex to the vertex list\n    @vertices << val\n    # Add a row to the adjacency matrix\n    new_row = Array.new(n, 0)\n    @adj_mat << new_row\n    # Add a column to the adjacency matrix\n    @adj_mat.each { |row| row << 0 }\n  end\n\n  ### Delete vertex ###\n  def remove_vertex(index)\n    raise IndexError if index >= size\n\n    # Remove the vertex at index from the vertex list\n    @vertices.delete_at(index)\n    # Remove the row at index from the adjacency matrix\n    @adj_mat.delete_at(index)\n    # Remove the column at index from the adjacency matrix\n    @adj_mat.each { |row| row.delete_at(index) }\n  end\n\n  ### Add edge ###\n  def add_edge(i, j)\n    # Parameters i, j correspond to the vertices element indices\n    # Handle index out of bounds and equality\n    if i < 0 || j < 0 || i >= size || j >= size || i == j\n      raise IndexError\n    end\n    # In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n    @adj_mat[i][j] = 1\n    @adj_mat[j][i] = 1\n  end\n\n  ### Delete edge ###\n  def remove_edge(i, j)\n    # Parameters i, j correspond to the vertices element indices\n    # Handle index out of bounds and equality\n    if i < 0 || j < 0 || i >= size || j >= size || i == j\n      raise IndexError\n    end\n    @adj_mat[i][j] = 0\n    @adj_mat[j][i] = 0\n  end\n\n  ### Print adjacency matrix ###\n  def __print__\n    puts \"Vertex list = #{@vertices}\"\n    puts 'Adjacency matrix ='\n    print_matrix(@adj_mat)\n  end\nend\n
","path":["Chapter 9. Graph","9.2   Basic Operations on Graphs"],"tags":[]},{"location":"chapter_graph/graph_operations/#922-implementation-based-on-adjacency-list","level":2,"title":"9.2.2   Implementation Based on Adjacency List","text":"

Given an undirected graph with a total of \\(n\\) vertices and \\(m\\) edges, the various operations can be implemented as shown in Figure 9-8.

  • Adding an edge: Add the edge at the end of the corresponding vertex's linked list, using \\(O(1)\\) time. Since it is an undirected graph, edges in both directions need to be added simultaneously.
  • Removing an edge: Find and remove the specified edge in the corresponding vertex's linked list, using \\(O(m)\\) time. In an undirected graph, edges in both directions need to be removed simultaneously.
  • Adding a vertex: Add a linked list in the adjacency list and set the new vertex as the head node of the list, using \\(O(1)\\) time.
  • Removing a vertex: Traverse the entire adjacency list and remove all edges containing the specified vertex, using \\(O(n + m)\\) time.
  • Initialization: Create \\(n\\) vertices and \\(2m\\) edges in the adjacency list, using \\(O(n + m)\\) time.
Initialize adjacency listAdd an edgeRemove an edgeAdd a vertexRemove a vertex

Figure 9-8   Initialization, adding and removing edges, adding and removing vertices in adjacency list

The following is the adjacency list code implementation. Compared to Figure 9-8, the actual code has the following differences.

  • For convenience in adding and removing vertices, and to simplify the code, we use lists (dynamic arrays) instead of linked lists.
  • A hash table is used to store the adjacency list, where key is the vertex instance and value is the list (linked list) of adjacent vertices for that vertex.

Additionally, we use the Vertex class to represent vertices in the adjacency list. The reason for this is: if we used list indices to distinguish different vertices as with adjacency matrices, then to delete the vertex at index \\(i\\), we would need to traverse the entire adjacency list and decrement all indices greater than \\(i\\) by \\(1\\), which is very inefficient. However, if each vertex is a unique Vertex instance, deleting a vertex does not require modifying other vertices.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_adjacency_list.py
class GraphAdjList:\n    \"\"\"Undirected graph class based on adjacency list\"\"\"\n\n    def __init__(self, edges: list[list[Vertex]]):\n        \"\"\"Constructor\"\"\"\n        # Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n        self.adj_list = dict[Vertex, list[Vertex]]()\n        # Add all vertices and edges\n        for edge in edges:\n            self.add_vertex(edge[0])\n            self.add_vertex(edge[1])\n            self.add_edge(edge[0], edge[1])\n\n    def size(self) -> int:\n        \"\"\"Get the number of vertices\"\"\"\n        return len(self.adj_list)\n\n    def add_edge(self, vet1: Vertex, vet2: Vertex):\n        \"\"\"Add edge\"\"\"\n        if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:\n            raise ValueError()\n        # Add edge vet1 - vet2\n        self.adj_list[vet1].append(vet2)\n        self.adj_list[vet2].append(vet1)\n\n    def remove_edge(self, vet1: Vertex, vet2: Vertex):\n        \"\"\"Remove edge\"\"\"\n        if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:\n            raise ValueError()\n        # Remove edge vet1 - vet2\n        self.adj_list[vet1].remove(vet2)\n        self.adj_list[vet2].remove(vet1)\n\n    def add_vertex(self, vet: Vertex):\n        \"\"\"Add vertex\"\"\"\n        if vet in self.adj_list:\n            return\n        # Add a new linked list in the adjacency list\n        self.adj_list[vet] = []\n\n    def remove_vertex(self, vet: Vertex):\n        \"\"\"Remove vertex\"\"\"\n        if vet not in self.adj_list:\n            raise ValueError()\n        # Remove the linked list corresponding to vertex vet in the adjacency list\n        self.adj_list.pop(vet)\n        # Traverse the linked lists of other vertices and remove all edges containing vet\n        for vertex in self.adj_list:\n            if vet in self.adj_list[vertex]:\n                self.adj_list[vertex].remove(vet)\n\n    def print(self):\n        \"\"\"Print adjacency list\"\"\"\n        print(\"Adjacency list =\")\n        for vertex in self.adj_list:\n            tmp = [v.val for v in self.adj_list[vertex]]\n            print(f\"{vertex.val}: {tmp},\")\n
graph_adjacency_list.cpp
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n  public:\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    unordered_map<Vertex *, vector<Vertex *>> adjList;\n\n    /* Remove specified node from vector */\n    void remove(vector<Vertex *> &vec, Vertex *vet) {\n        for (int i = 0; i < vec.size(); i++) {\n            if (vec[i] == vet) {\n                vec.erase(vec.begin() + i);\n                break;\n            }\n        }\n    }\n\n    /* Constructor */\n    GraphAdjList(const vector<vector<Vertex *>> &edges) {\n        // Add all vertices and edges\n        for (const vector<Vertex *> &edge : edges) {\n            addVertex(edge[0]);\n            addVertex(edge[1]);\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int size() {\n        return adjList.size();\n    }\n\n    /* Add edge */\n    void addEdge(Vertex *vet1, Vertex *vet2) {\n        if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)\n            throw invalid_argument(\"Vertex does not exist\");\n        // Add edge vet1 - vet2\n        adjList[vet1].push_back(vet2);\n        adjList[vet2].push_back(vet1);\n    }\n\n    /* Remove edge */\n    void removeEdge(Vertex *vet1, Vertex *vet2) {\n        if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)\n            throw invalid_argument(\"Vertex does not exist\");\n        // Remove edge vet1 - vet2\n        remove(adjList[vet1], vet2);\n        remove(adjList[vet2], vet1);\n    }\n\n    /* Add vertex */\n    void addVertex(Vertex *vet) {\n        if (adjList.count(vet))\n            return;\n        // Add a new linked list in the adjacency list\n        adjList[vet] = vector<Vertex *>();\n    }\n\n    /* Remove vertex */\n    void removeVertex(Vertex *vet) {\n        if (!adjList.count(vet))\n            throw invalid_argument(\"Vertex does not exist\");\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        adjList.erase(vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for (auto &adj : adjList) {\n            remove(adj.second, vet);\n        }\n    }\n\n    /* Print adjacency list */\n    void print() {\n        cout << \"Adjacency list =\" << endl;\n        for (auto &adj : adjList) {\n            const auto &key = adj.first;\n            const auto &vec = adj.second;\n            cout << key->val << \": \";\n            printVector(vetsToVals(vec));\n        }\n    }\n};\n
graph_adjacency_list.java
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    Map<Vertex, List<Vertex>> adjList;\n\n    /* Constructor */\n    public GraphAdjList(Vertex[][] edges) {\n        this.adjList = new HashMap<>();\n        // Add all vertices and edges\n        for (Vertex[] edge : edges) {\n            addVertex(edge[0]);\n            addVertex(edge[1]);\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    public int size() {\n        return adjList.size();\n    }\n\n    /* Add edge */\n    public void addEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw new IllegalArgumentException();\n        // Add edge vet1 - vet2\n        adjList.get(vet1).add(vet2);\n        adjList.get(vet2).add(vet1);\n    }\n\n    /* Remove edge */\n    public void removeEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw new IllegalArgumentException();\n        // Remove edge vet1 - vet2\n        adjList.get(vet1).remove(vet2);\n        adjList.get(vet2).remove(vet1);\n    }\n\n    /* Add vertex */\n    public void addVertex(Vertex vet) {\n        if (adjList.containsKey(vet))\n            return;\n        // Add a new linked list in the adjacency list\n        adjList.put(vet, new ArrayList<>());\n    }\n\n    /* Remove vertex */\n    public void removeVertex(Vertex vet) {\n        if (!adjList.containsKey(vet))\n            throw new IllegalArgumentException();\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        adjList.remove(vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for (List<Vertex> list : adjList.values()) {\n            list.remove(vet);\n        }\n    }\n\n    /* Print adjacency list */\n    public void print() {\n        System.out.println(\"Adjacency list =\");\n        for (Map.Entry<Vertex, List<Vertex>> pair : adjList.entrySet()) {\n            List<Integer> tmp = new ArrayList<>();\n            for (Vertex vertex : pair.getValue())\n                tmp.add(vertex.val);\n            System.out.println(pair.getKey().val + \": \" + tmp + \",\");\n        }\n    }\n}\n
graph_adjacency_list.cs
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    public Dictionary<Vertex, List<Vertex>> adjList;\n\n    /* Constructor */\n    public GraphAdjList(Vertex[][] edges) {\n        adjList = [];\n        // Add all vertices and edges\n        foreach (Vertex[] edge in edges) {\n            AddVertex(edge[0]);\n            AddVertex(edge[1]);\n            AddEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int Size() {\n        return adjList.Count;\n    }\n\n    /* Add edge */\n    public void AddEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2)\n            throw new InvalidOperationException();\n        // Add edge vet1 - vet2\n        adjList[vet1].Add(vet2);\n        adjList[vet2].Add(vet1);\n    }\n\n    /* Remove edge */\n    public void RemoveEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2)\n            throw new InvalidOperationException();\n        // Remove edge vet1 - vet2\n        adjList[vet1].Remove(vet2);\n        adjList[vet2].Remove(vet1);\n    }\n\n    /* Add vertex */\n    public void AddVertex(Vertex vet) {\n        if (adjList.ContainsKey(vet))\n            return;\n        // Add a new linked list in the adjacency list\n        adjList.Add(vet, []);\n    }\n\n    /* Remove vertex */\n    public void RemoveVertex(Vertex vet) {\n        if (!adjList.ContainsKey(vet))\n            throw new InvalidOperationException();\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        adjList.Remove(vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        foreach (List<Vertex> list in adjList.Values) {\n            list.Remove(vet);\n        }\n    }\n\n    /* Print adjacency list */\n    public void Print() {\n        Console.WriteLine(\"Adjacency list =\");\n        foreach (KeyValuePair<Vertex, List<Vertex>> pair in adjList) {\n            List<int> tmp = [];\n            foreach (Vertex vertex in pair.Value)\n                tmp.Add(vertex.val);\n            Console.WriteLine(pair.Key.val + \": [\" + string.Join(\", \", tmp) + \"],\");\n        }\n    }\n}\n
graph_adjacency_list.go
/* Undirected graph class based on adjacency list */\ntype graphAdjList struct {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    adjList map[Vertex][]Vertex\n}\n\n/* Constructor */\nfunc newGraphAdjList(edges [][]Vertex) *graphAdjList {\n    g := &graphAdjList{\n        adjList: make(map[Vertex][]Vertex),\n    }\n    // Add all vertices and edges\n    for _, edge := range edges {\n        g.addVertex(edge[0])\n        g.addVertex(edge[1])\n        g.addEdge(edge[0], edge[1])\n    }\n    return g\n}\n\n/* Get the number of vertices */\nfunc (g *graphAdjList) size() int {\n    return len(g.adjList)\n}\n\n/* Add edge */\nfunc (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) {\n    _, ok1 := g.adjList[vet1]\n    _, ok2 := g.adjList[vet2]\n    if !ok1 || !ok2 || vet1 == vet2 {\n        panic(\"error\")\n    }\n    // Add edge vet1 - vet2, add anonymous struct{},\n    g.adjList[vet1] = append(g.adjList[vet1], vet2)\n    g.adjList[vet2] = append(g.adjList[vet2], vet1)\n}\n\n/* Remove edge */\nfunc (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) {\n    _, ok1 := g.adjList[vet1]\n    _, ok2 := g.adjList[vet2]\n    if !ok1 || !ok2 || vet1 == vet2 {\n        panic(\"error\")\n    }\n    // Remove edge vet1 - vet2\n    g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2)\n    g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1)\n}\n\n/* Add vertex */\nfunc (g *graphAdjList) addVertex(vet Vertex) {\n    _, ok := g.adjList[vet]\n    if ok {\n        return\n    }\n    // Add a new linked list in the adjacency list\n    g.adjList[vet] = make([]Vertex, 0)\n}\n\n/* Remove vertex */\nfunc (g *graphAdjList) removeVertex(vet Vertex) {\n    _, ok := g.adjList[vet]\n    if !ok {\n        panic(\"error\")\n    }\n    // Remove the linked list corresponding to vertex vet in the adjacency list\n    delete(g.adjList, vet)\n    // Traverse the linked lists of other vertices and remove all edges containing vet\n    for v, list := range g.adjList {\n        g.adjList[v] = DeleteSliceElms(list, vet)\n    }\n}\n\n/* Print adjacency list */\nfunc (g *graphAdjList) print() {\n    var builder strings.Builder\n    fmt.Printf(\"Adjacency list = \\n\")\n    for k, v := range g.adjList {\n        builder.WriteString(\"\\t\\t\" + strconv.Itoa(k.Val) + \": \")\n        for _, vet := range v {\n            builder.WriteString(strconv.Itoa(vet.Val) + \" \")\n        }\n        fmt.Println(builder.String())\n        builder.Reset()\n    }\n}\n
graph_adjacency_list.swift
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    public private(set) var adjList: [Vertex: [Vertex]]\n\n    /* Constructor */\n    public init(edges: [[Vertex]]) {\n        adjList = [:]\n        // Add all vertices and edges\n        for edge in edges {\n            addVertex(vet: edge[0])\n            addVertex(vet: edge[1])\n            addEdge(vet1: edge[0], vet2: edge[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    public func size() -> Int {\n        adjList.count\n    }\n\n    /* Add edge */\n    public func addEdge(vet1: Vertex, vet2: Vertex) {\n        if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\n            fatalError(\"Invalid parameter\")\n        }\n        // Add edge vet1 - vet2\n        adjList[vet1]?.append(vet2)\n        adjList[vet2]?.append(vet1)\n    }\n\n    /* Remove edge */\n    public func removeEdge(vet1: Vertex, vet2: Vertex) {\n        if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\n            fatalError(\"Invalid parameter\")\n        }\n        // Remove edge vet1 - vet2\n        adjList[vet1]?.removeAll { $0 == vet2 }\n        adjList[vet2]?.removeAll { $0 == vet1 }\n    }\n\n    /* Add vertex */\n    public func addVertex(vet: Vertex) {\n        if adjList[vet] != nil {\n            return\n        }\n        // Add a new linked list in the adjacency list\n        adjList[vet] = []\n    }\n\n    /* Remove vertex */\n    public func removeVertex(vet: Vertex) {\n        if adjList[vet] == nil {\n            fatalError(\"Invalid parameter\")\n        }\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        adjList.removeValue(forKey: vet)\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for key in adjList.keys {\n            adjList[key]?.removeAll { $0 == vet }\n        }\n    }\n\n    /* Print adjacency list */\n    public func print() {\n        Swift.print(\"Adjacency list =\")\n        for (vertex, list) in adjList {\n            let list = list.map { $0.val }\n            Swift.print(\"\\(vertex.val): \\(list),\")\n        }\n    }\n}\n
graph_adjacency_list.js
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    adjList;\n\n    /* Constructor */\n    constructor(edges) {\n        this.adjList = new Map();\n        // Add all vertices and edges\n        for (const edge of edges) {\n            this.addVertex(edge[0]);\n            this.addVertex(edge[1]);\n            this.addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size() {\n        return this.adjList.size;\n    }\n\n    /* Add edge */\n    addEdge(vet1, vet2) {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2\n        ) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Add edge vet1 - vet2\n        this.adjList.get(vet1).push(vet2);\n        this.adjList.get(vet2).push(vet1);\n    }\n\n    /* Remove edge */\n    removeEdge(vet1, vet2) {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2 ||\n            this.adjList.get(vet1).indexOf(vet2) === -1\n        ) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Remove edge vet1 - vet2\n        this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1);\n        this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1);\n    }\n\n    /* Add vertex */\n    addVertex(vet) {\n        if (this.adjList.has(vet)) return;\n        // Add a new linked list in the adjacency list\n        this.adjList.set(vet, []);\n    }\n\n    /* Remove vertex */\n    removeVertex(vet) {\n        if (!this.adjList.has(vet)) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        this.adjList.delete(vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for (const set of this.adjList.values()) {\n            const index = set.indexOf(vet);\n            if (index > -1) {\n                set.splice(index, 1);\n            }\n        }\n    }\n\n    /* Print adjacency list */\n    print() {\n        console.log('Adjacency list =');\n        for (const [key, value] of this.adjList) {\n            const tmp = [];\n            for (const vertex of value) {\n                tmp.push(vertex.val);\n            }\n            console.log(key.val + ': ' + tmp.join());\n        }\n    }\n}\n
graph_adjacency_list.ts
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    adjList: Map<Vertex, Vertex[]>;\n\n    /* Constructor */\n    constructor(edges: Vertex[][]) {\n        this.adjList = new Map();\n        // Add all vertices and edges\n        for (const edge of edges) {\n            this.addVertex(edge[0]);\n            this.addVertex(edge[1]);\n            this.addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size(): number {\n        return this.adjList.size;\n    }\n\n    /* Add edge */\n    addEdge(vet1: Vertex, vet2: Vertex): void {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2\n        ) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Add edge vet1 - vet2\n        this.adjList.get(vet1).push(vet2);\n        this.adjList.get(vet2).push(vet1);\n    }\n\n    /* Remove edge */\n    removeEdge(vet1: Vertex, vet2: Vertex): void {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2 ||\n            this.adjList.get(vet1).indexOf(vet2) === -1\n        ) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Remove edge vet1 - vet2\n        this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1);\n        this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1);\n    }\n\n    /* Add vertex */\n    addVertex(vet: Vertex): void {\n        if (this.adjList.has(vet)) return;\n        // Add a new linked list in the adjacency list\n        this.adjList.set(vet, []);\n    }\n\n    /* Remove vertex */\n    removeVertex(vet: Vertex): void {\n        if (!this.adjList.has(vet)) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        this.adjList.delete(vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for (const set of this.adjList.values()) {\n            const index: number = set.indexOf(vet);\n            if (index > -1) {\n                set.splice(index, 1);\n            }\n        }\n    }\n\n    /* Print adjacency list */\n    print(): void {\n        console.log('Adjacency list =');\n        for (const [key, value] of this.adjList.entries()) {\n            const tmp = [];\n            for (const vertex of value) {\n                tmp.push(vertex.val);\n            }\n            console.log(key.val + ': ' + tmp.join());\n        }\n    }\n}\n
graph_adjacency_list.dart
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n  // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n  Map<Vertex, List<Vertex>> adjList = {};\n\n  /* Constructor */\n  GraphAdjList(List<List<Vertex>> edges) {\n    for (List<Vertex> edge in edges) {\n      addVertex(edge[0]);\n      addVertex(edge[1]);\n      addEdge(edge[0], edge[1]);\n    }\n  }\n\n  /* Get the number of vertices */\n  int size() {\n    return adjList.length;\n  }\n\n  /* Add edge */\n  void addEdge(Vertex vet1, Vertex vet2) {\n    if (!adjList.containsKey(vet1) ||\n        !adjList.containsKey(vet2) ||\n        vet1 == vet2) {\n      throw ArgumentError;\n    }\n    // Add edge vet1 - vet2\n    adjList[vet1]!.add(vet2);\n    adjList[vet2]!.add(vet1);\n  }\n\n  /* Remove edge */\n  void removeEdge(Vertex vet1, Vertex vet2) {\n    if (!adjList.containsKey(vet1) ||\n        !adjList.containsKey(vet2) ||\n        vet1 == vet2) {\n      throw ArgumentError;\n    }\n    // Remove edge vet1 - vet2\n    adjList[vet1]!.remove(vet2);\n    adjList[vet2]!.remove(vet1);\n  }\n\n  /* Add vertex */\n  void addVertex(Vertex vet) {\n    if (adjList.containsKey(vet)) return;\n    // Add a new linked list in the adjacency list\n    adjList[vet] = [];\n  }\n\n  /* Remove vertex */\n  void removeVertex(Vertex vet) {\n    if (!adjList.containsKey(vet)) {\n      throw ArgumentError;\n    }\n    // Remove the linked list corresponding to vertex vet in the adjacency list\n    adjList.remove(vet);\n    // Traverse the linked lists of other vertices and remove all edges containing vet\n    adjList.forEach((key, value) {\n      value.remove(vet);\n    });\n  }\n\n  /* Print adjacency list */\n  void printAdjList() {\n    print(\"Adjacency list =\");\n    adjList.forEach((key, value) {\n      List<int> tmp = [];\n      for (Vertex vertex in value) {\n        tmp.add(vertex.val);\n      }\n      print(\"${key.val}: $tmp,\");\n    });\n  }\n}\n
graph_adjacency_list.rs
/* Undirected graph type based on adjacency list */\npub struct GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    pub adj_list: HashMap<Vertex, Vec<Vertex>>, // maybe HashSet<Vertex> for value part is better?\n}\n\nimpl GraphAdjList {\n    /* Constructor */\n    pub fn new(edges: Vec<[Vertex; 2]>) -> Self {\n        let mut graph = GraphAdjList {\n            adj_list: HashMap::new(),\n        };\n        // Add all vertices and edges\n        for edge in edges {\n            graph.add_vertex(edge[0]);\n            graph.add_vertex(edge[1]);\n            graph.add_edge(edge[0], edge[1]);\n        }\n\n        graph\n    }\n\n    /* Get the number of vertices */\n    #[allow(unused)]\n    pub fn size(&self) -> usize {\n        self.adj_list.len()\n    }\n\n    /* Add edge */\n    pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) {\n        if vet1 == vet2 {\n            panic!(\"value error\");\n        }\n        // Add edge vet1 - vet2\n        self.adj_list.entry(vet1).or_default().push(vet2);\n        self.adj_list.entry(vet2).or_default().push(vet1);\n    }\n\n    /* Remove edge */\n    #[allow(unused)]\n    pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) {\n        if vet1 == vet2 {\n            panic!(\"value error\");\n        }\n        // Remove edge vet1 - vet2\n        self.adj_list\n            .entry(vet1)\n            .and_modify(|v| v.retain(|&e| e != vet2));\n        self.adj_list\n            .entry(vet2)\n            .and_modify(|v| v.retain(|&e| e != vet1));\n    }\n\n    /* Add vertex */\n    pub fn add_vertex(&mut self, vet: Vertex) {\n        if self.adj_list.contains_key(&vet) {\n            return;\n        }\n        // Add a new linked list in the adjacency list\n        self.adj_list.insert(vet, vec![]);\n    }\n\n    /* Remove vertex */\n    #[allow(unused)]\n    pub fn remove_vertex(&mut self, vet: Vertex) {\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        self.adj_list.remove(&vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for list in self.adj_list.values_mut() {\n            list.retain(|&v| v != vet);\n        }\n    }\n\n    /* Print adjacency list */\n    pub fn print(&self) {\n        println!(\"Adjacency list =\");\n        for (vertex, list) in &self.adj_list {\n            let list = list.iter().map(|vertex| vertex.val).collect::<Vec<i32>>();\n            println!(\"{}: {:?},\", vertex.val, list);\n        }\n    }\n}\n
graph_adjacency_list.c
/* Node structure */\ntypedef struct AdjListNode {\n    Vertex *vertex;           // Vertex\n    struct AdjListNode *next; // Successor node\n} AdjListNode;\n\n/* Find node corresponding to vertex */\nAdjListNode *findNode(GraphAdjList *graph, Vertex *vet) {\n    for (int i = 0; i < graph->size; i++) {\n        if (graph->heads[i]->vertex == vet) {\n            return graph->heads[i];\n        }\n    }\n    return NULL;\n}\n\n/* Add edge helper function */\nvoid addEdgeHelper(AdjListNode *head, Vertex *vet) {\n    AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode));\n    node->vertex = vet;\n    // Head insertion\n    node->next = head->next;\n    head->next = node;\n}\n\n/* Remove edge helper function */\nvoid removeEdgeHelper(AdjListNode *head, Vertex *vet) {\n    AdjListNode *pre = head;\n    AdjListNode *cur = head->next;\n    // Search for node corresponding to vet in list\n    while (cur != NULL && cur->vertex != vet) {\n        pre = cur;\n        cur = cur->next;\n    }\n    if (cur == NULL)\n        return;\n    // Remove node corresponding to vet from list\n    pre->next = cur->next;\n    // Free memory\n    free(cur);\n}\n\n/* Undirected graph class based on adjacency list */\ntypedef struct {\n    AdjListNode *heads[MAX_SIZE]; // Node array\n    int size;                     // Node count\n} GraphAdjList;\n\n/* Constructor */\nGraphAdjList *newGraphAdjList() {\n    GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList));\n    if (!graph) {\n        return NULL;\n    }\n    graph->size = 0;\n    for (int i = 0; i < MAX_SIZE; i++) {\n        graph->heads[i] = NULL;\n    }\n    return graph;\n}\n\n/* Destructor */\nvoid delGraphAdjList(GraphAdjList *graph) {\n    for (int i = 0; i < graph->size; i++) {\n        AdjListNode *cur = graph->heads[i];\n        while (cur != NULL) {\n            AdjListNode *next = cur->next;\n            if (cur != graph->heads[i]) {\n                free(cur);\n            }\n            cur = next;\n        }\n        free(graph->heads[i]->vertex);\n        free(graph->heads[i]);\n    }\n    free(graph);\n}\n\n/* Find node corresponding to vertex */\nAdjListNode *findNode(GraphAdjList *graph, Vertex *vet) {\n    for (int i = 0; i < graph->size; i++) {\n        if (graph->heads[i]->vertex == vet) {\n            return graph->heads[i];\n        }\n    }\n    return NULL;\n}\n\n/* Add edge */\nvoid addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {\n    AdjListNode *head1 = findNode(graph, vet1);\n    AdjListNode *head2 = findNode(graph, vet2);\n    assert(head1 != NULL && head2 != NULL && head1 != head2);\n    // Add edge vet1 - vet2\n    addEdgeHelper(head1, vet2);\n    addEdgeHelper(head2, vet1);\n}\n\n/* Remove edge */\nvoid removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {\n    AdjListNode *head1 = findNode(graph, vet1);\n    AdjListNode *head2 = findNode(graph, vet2);\n    assert(head1 != NULL && head2 != NULL);\n    // Remove edge vet1 - vet2\n    removeEdgeHelper(head1, head2->vertex);\n    removeEdgeHelper(head2, head1->vertex);\n}\n\n/* Add vertex */\nvoid addVertex(GraphAdjList *graph, Vertex *vet) {\n    assert(graph != NULL && graph->size < MAX_SIZE);\n    AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode));\n    head->vertex = vet;\n    head->next = NULL;\n    // Add a new linked list in the adjacency list\n    graph->heads[graph->size++] = head;\n}\n\n/* Remove vertex */\nvoid removeVertex(GraphAdjList *graph, Vertex *vet) {\n    AdjListNode *node = findNode(graph, vet);\n    assert(node != NULL);\n    // Remove the linked list corresponding to vertex vet in the adjacency list\n    AdjListNode *cur = node, *pre = NULL;\n    while (cur) {\n        pre = cur;\n        cur = cur->next;\n        free(pre);\n    }\n    // Traverse the linked lists of other vertices and remove all edges containing vet\n    for (int i = 0; i < graph->size; i++) {\n        cur = graph->heads[i];\n        pre = NULL;\n        while (cur) {\n            pre = cur;\n            cur = cur->next;\n            if (cur && cur->vertex == vet) {\n                pre->next = cur->next;\n                free(cur);\n                break;\n            }\n        }\n    }\n    // Move vertices after this vertex forward to fill gap\n    int i;\n    for (i = 0; i < graph->size; i++) {\n        if (graph->heads[i] == node)\n            break;\n    }\n    for (int j = i; j < graph->size - 1; j++) {\n        graph->heads[j] = graph->heads[j + 1];\n    }\n    graph->size--;\n    free(vet);\n}\n
graph_adjacency_list.kt
/* Undirected graph class based on adjacency list */\nclass GraphAdjList(edges: Array<Array<Vertex?>>) {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    val adjList = HashMap<Vertex, MutableList<Vertex>>()\n\n    /* Constructor */\n    init {\n        // Add all vertices and edges\n        for (edge in edges) {\n            addVertex(edge[0]!!)\n            addVertex(edge[1]!!)\n            addEdge(edge[0]!!, edge[1]!!)\n        }\n    }\n\n    /* Get the number of vertices */\n    fun size(): Int {\n        return adjList.size\n    }\n\n    /* Add edge */\n    fun addEdge(vet1: Vertex, vet2: Vertex) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw IllegalArgumentException()\n        // Add edge vet1 - vet2\n        adjList[vet1]?.add(vet2)\n        adjList[vet2]?.add(vet1)\n    }\n\n    /* Remove edge */\n    fun removeEdge(vet1: Vertex, vet2: Vertex) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw IllegalArgumentException()\n        // Remove edge vet1 - vet2\n        adjList[vet1]?.remove(vet2)\n        adjList[vet2]?.remove(vet1)\n    }\n\n    /* Add vertex */\n    fun addVertex(vet: Vertex) {\n        if (adjList.containsKey(vet))\n            return\n        // Add a new linked list in the adjacency list\n        adjList[vet] = mutableListOf()\n    }\n\n    /* Remove vertex */\n    fun removeVertex(vet: Vertex) {\n        if (!adjList.containsKey(vet))\n            throw IllegalArgumentException()\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        adjList.remove(vet)\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for (list in adjList.values) {\n            list.remove(vet)\n        }\n    }\n\n    /* Print adjacency list */\n    fun print() {\n        println(\"Adjacency list =\")\n        for (pair in adjList.entries) {\n            val tmp = mutableListOf<Int>()\n            for (vertex in pair.value) {\n                tmp.add(vertex._val)\n            }\n            println(\"${pair.key._val}: $tmp,\")\n        }\n    }\n}\n
graph_adjacency_list.rb
### Undirected graph class based on adjacency list ###\nclass GraphAdjList\n  attr_reader :adj_list\n\n  ### Constructor ###\n  def initialize(edges)\n    # Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    @adj_list = {}\n    # Add all vertices and edges\n    for edge in edges\n      add_vertex(edge[0])\n      add_vertex(edge[1])\n      add_edge(edge[0], edge[1])\n    end\n  end\n\n  ### Get number of vertices ###\n  def size\n    @adj_list.length\n  end\n\n  ### Add edge ###\n  def add_edge(vet1, vet2)\n    raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)\n\n    @adj_list[vet1] << vet2\n    @adj_list[vet2] << vet1\n  end\n\n  ### Delete edge ###\n  def remove_edge(vet1, vet2)\n    raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)\n\n    # Remove edge vet1 - vet2\n    @adj_list[vet1].delete(vet2)\n    @adj_list[vet2].delete(vet1)\n  end\n\n  ### Add vertex ###\n  def add_vertex(vet)\n    return if @adj_list.include?(vet)\n\n    # Add a new linked list in the adjacency list\n    @adj_list[vet] = []\n  end\n\n  ### Delete vertex ###\n  def remove_vertex(vet)\n    raise ArgumentError unless @adj_list.include?(vet)\n\n    # Remove the linked list corresponding to vertex vet in the adjacency list\n    @adj_list.delete(vet)\n    # Traverse the linked lists of other vertices and remove all edges containing vet\n    for vertex in @adj_list\n      @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet)\n    end\n  end\n\n  ### Print adjacency list ###\n  def __print__\n    puts 'Adjacency list ='\n    for vertex in @adj_list\n      tmp = @adj_list[vertex.first].map { |v| v.val }\n      puts \"#{vertex.first.val}: #{tmp},\"\n    end\n  end\nend\n
","path":["Chapter 9. Graph","9.2   Basic Operations on Graphs"],"tags":[]},{"location":"chapter_graph/graph_operations/#923-efficiency-comparison","level":2,"title":"9.2.3   Efficiency Comparison","text":"

Assuming the graph has \\(n\\) vertices and \\(m\\) edges, Table 9-2 compares the time efficiency and space efficiency of adjacency matrices and adjacency lists. Note that the adjacency list (linked list) corresponds to the implementation in this text, while the adjacency list (hash table) refers specifically to the implementation where all linked lists are replaced with hash tables.

Table 9-2   Comparison of adjacency matrix and adjacency list

Adjacency matrix Adjacency list (linked list) Adjacency list (hash table) Determine adjacency \\(O(1)\\) \\(O(n)\\) \\(O(1)\\) Add an edge \\(O(1)\\) \\(O(1)\\) \\(O(1)\\) Remove an edge \\(O(1)\\) \\(O(n)\\) \\(O(1)\\) Add a vertex \\(O(n)\\) \\(O(1)\\) \\(O(1)\\) Remove a vertex \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n)\\) Memory space usage \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n + m)\\)

Observing Table 9-2, it appears that the adjacency list (hash table) has the best time efficiency and space efficiency. However, in practice, operating on edges in the adjacency matrix is more efficient, requiring only a single array access or assignment operation. Overall, adjacency matrices embody the principle of \"trading space for time\", while adjacency lists embody \"trading time for space\".

","path":["Chapter 9. Graph","9.2   Basic Operations on Graphs"],"tags":[]},{"location":"chapter_graph/graph_traversal/","level":1,"title":"9.3   Graph Traversal","text":"

Trees represent \"one-to-many\" relationships, while graphs have a higher degree of freedom and can represent any \"many-to-many\" relationships. Therefore, we can view trees as a special case of graphs. Clearly, tree traversal operations are also a special case of graph traversal operations.

Both graphs and trees require the application of search algorithms to implement traversal operations. Graph traversal methods can also be divided into two types: breadth-first traversal and depth-first traversal.

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#931-breadth-first-search","level":2,"title":"9.3.1   Breadth-First Search","text":"

Breadth-first search is a near-to-far traversal method that, starting from a certain node, always prioritizes visiting the nearest vertices and expands outward layer by layer. As shown in Figure 9-9, starting from the top-left vertex, first traverse all adjacent vertices of that vertex, then traverse all adjacent vertices of the next vertex, and so on, until all vertices have been visited.

Figure 9-9   Breadth-first search of a graph

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#1-algorithm-implementation","level":3,"title":"1.   Algorithm Implementation","text":"

BFS is typically implemented with the help of a queue, as shown in the code below. The queue has a \"first in, first out\" property, which aligns with the BFS idea of \"near to far\".

  1. Add the starting vertex startVet to the queue and begin the loop.
  2. In each iteration of the loop, pop the vertex at the front of the queue and record it as visited, then add all adjacent vertices of that vertex to the back of the queue.
  3. Repeat step 2. until all vertices have been visited.

To prevent revisiting vertices, we use a hash set visited to record which nodes have been visited.

Tip

A hash set can be viewed as a hash table that stores only key without storing value. It can perform addition, deletion, lookup, and modification operations on key in \\(O(1)\\) time complexity. Based on the uniqueness of key, hash sets are typically used for data deduplication and similar scenarios.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_bfs.py
def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:\n    \"\"\"Breadth-first traversal\"\"\"\n    # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\n    # Vertex traversal sequence\n    res = []\n    # Hash set for recording vertices that have been visited\n    visited = set[Vertex]([start_vet])\n    # Queue used to implement BFS\n    que = deque[Vertex]([start_vet])\n    # Starting from vertex vet, loop until all vertices are visited\n    while len(que) > 0:\n        vet = que.popleft()  # Dequeue the front vertex\n        res.append(vet)  # Record visited vertex\n        # Traverse all adjacent vertices of this vertex\n        for adj_vet in graph.adj_list[vet]:\n            if adj_vet in visited:\n                continue  # Skip vertices that have been visited\n            que.append(adj_vet)  # Only enqueue unvisited vertices\n            visited.add(adj_vet)  # Mark this vertex as visited\n    # Return vertex traversal sequence\n    return res\n
graph_bfs.cpp
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nvector<Vertex *> graphBFS(GraphAdjList &graph, Vertex *startVet) {\n    // Vertex traversal sequence\n    vector<Vertex *> res;\n    // Hash set for recording vertices that have been visited\n    unordered_set<Vertex *> visited = {startVet};\n    // Queue used to implement BFS\n    queue<Vertex *> que;\n    que.push(startVet);\n    // Starting from vertex vet, loop until all vertices are visited\n    while (!que.empty()) {\n        Vertex *vet = que.front();\n        que.pop();          // Dequeue the front vertex\n        res.push_back(vet); // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for (auto adjVet : graph.adjList[vet]) {\n            if (visited.count(adjVet))\n                continue;            // Skip vertices that have been visited\n            que.push(adjVet);        // Only enqueue unvisited vertices\n            visited.emplace(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res;\n}\n
graph_bfs.java
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nList<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = new ArrayList<>();\n    // Hash set for recording vertices that have been visited\n    Set<Vertex> visited = new HashSet<>();\n    visited.add(startVet);\n    // Queue used to implement BFS\n    Queue<Vertex> que = new LinkedList<>();\n    que.offer(startVet);\n    // Starting from vertex vet, loop until all vertices are visited\n    while (!que.isEmpty()) {\n        Vertex vet = que.poll(); // Dequeue the front vertex\n        res.add(vet);            // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for (Vertex adjVet : graph.adjList.get(vet)) {\n            if (visited.contains(adjVet))\n                continue;        // Skip vertices that have been visited\n            que.offer(adjVet);   // Only enqueue unvisited vertices\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res;\n}\n
graph_bfs.cs
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nList<Vertex> GraphBFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = [];\n    // Hash set for recording vertices that have been visited\n    HashSet<Vertex> visited = [startVet];\n    // Queue used to implement BFS\n    Queue<Vertex> que = new();\n    que.Enqueue(startVet);\n    // Starting from vertex vet, loop until all vertices are visited\n    while (que.Count > 0) {\n        Vertex vet = que.Dequeue(); // Dequeue the front vertex\n        res.Add(vet);               // Record visited vertex\n        foreach (Vertex adjVet in graph.adjList[vet]) {\n            if (visited.Contains(adjVet)) {\n                continue;          // Skip vertices that have been visited\n            }\n            que.Enqueue(adjVet);   // Only enqueue unvisited vertices\n            visited.Add(adjVet);   // Mark this vertex as visited\n        }\n    }\n\n    // Return vertex traversal sequence\n    return res;\n}\n
graph_bfs.go
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunc graphBFS(g *graphAdjList, startVet Vertex) []Vertex {\n    // Vertex traversal sequence\n    res := make([]Vertex, 0)\n    // Hash set for recording vertices that have been visited\n    visited := make(map[Vertex]struct{})\n    visited[startVet] = struct{}{}\n    // Queue used to implement BFS, using slice to simulate queue\n    queue := make([]Vertex, 0)\n    queue = append(queue, startVet)\n    // Starting from vertex vet, loop until all vertices are visited\n    for len(queue) > 0 {\n        // Dequeue the front vertex\n        vet := queue[0]\n        queue = queue[1:]\n        // Record visited vertex\n        res = append(res, vet)\n        // Traverse all adjacent vertices of this vertex\n        for _, adjVet := range g.adjList[vet] {\n            _, isExist := visited[adjVet]\n            // Only enqueue unvisited vertices\n            if !isExist {\n                queue = append(queue, adjVet)\n                visited[adjVet] = struct{}{}\n            }\n        }\n    }\n    // Return vertex traversal sequence\n    return res\n}\n
graph_bfs.swift
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunc graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {\n    // Vertex traversal sequence\n    var res: [Vertex] = []\n    // Hash set for recording vertices that have been visited\n    var visited: Set<Vertex> = [startVet]\n    // Queue used to implement BFS\n    var que: [Vertex] = [startVet]\n    // Starting from vertex vet, loop until all vertices are visited\n    while !que.isEmpty {\n        let vet = que.removeFirst() // Dequeue the front vertex\n        res.append(vet) // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for adjVet in graph.adjList[vet] ?? [] {\n            if visited.contains(adjVet) {\n                continue // Skip vertices that have been visited\n            }\n            que.append(adjVet) // Only enqueue unvisited vertices\n            visited.insert(adjVet) // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res\n}\n
graph_bfs.js
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunction graphBFS(graph, startVet) {\n    // Vertex traversal sequence\n    const res = [];\n    // Hash set for recording vertices that have been visited\n    const visited = new Set();\n    visited.add(startVet);\n    // Queue used to implement BFS\n    const que = [startVet];\n    // Starting from vertex vet, loop until all vertices are visited\n    while (que.length) {\n        const vet = que.shift(); // Dequeue the front vertex\n        res.push(vet); // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for (const adjVet of graph.adjList.get(vet) ?? []) {\n            if (visited.has(adjVet)) {\n                continue; // Skip vertices that have been visited\n            }\n            que.push(adjVet); // Only enqueue unvisited vertices\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res;\n}\n
graph_bfs.ts
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunction graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {\n    // Vertex traversal sequence\n    const res: Vertex[] = [];\n    // Hash set for recording vertices that have been visited\n    const visited: Set<Vertex> = new Set();\n    visited.add(startVet);\n    // Queue used to implement BFS\n    const que = [startVet];\n    // Starting from vertex vet, loop until all vertices are visited\n    while (que.length) {\n        const vet = que.shift(); // Dequeue the front vertex\n        res.push(vet); // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for (const adjVet of graph.adjList.get(vet) ?? []) {\n            if (visited.has(adjVet)) {\n                continue; // Skip vertices that have been visited\n            }\n            que.push(adjVet); // Only enqueue unvisited\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res;\n}\n
graph_bfs.dart
/* Breadth-first traversal */\nList<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {\n  // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\n  // Vertex traversal sequence\n  List<Vertex> res = [];\n  // Hash set for recording vertices that have been visited\n  Set<Vertex> visited = {};\n  visited.add(startVet);\n  // Queue used to implement BFS\n  Queue<Vertex> que = Queue();\n  que.add(startVet);\n  // Starting from vertex vet, loop until all vertices are visited\n  while (que.isNotEmpty) {\n    Vertex vet = que.removeFirst(); // Dequeue the front vertex\n    res.add(vet); // Record visited vertex\n    // Traverse all adjacent vertices of this vertex\n    for (Vertex adjVet in graph.adjList[vet]!) {\n      if (visited.contains(adjVet)) {\n        continue; // Skip vertices that have been visited\n      }\n      que.add(adjVet); // Only enqueue unvisited vertices\n      visited.add(adjVet); // Mark this vertex as visited\n    }\n  }\n  // Return vertex traversal sequence\n  return res;\n}\n
graph_bfs.rs
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {\n    // Vertex traversal sequence\n    let mut res = vec![];\n    // Hash set for recording vertices that have been visited\n    let mut visited = HashSet::new();\n    visited.insert(start_vet);\n    // Queue used to implement BFS\n    let mut que = VecDeque::new();\n    que.push_back(start_vet);\n    // Starting from vertex vet, loop until all vertices are visited\n    while let Some(vet) = que.pop_front() {\n        res.push(vet); // Record visited vertex\n\n        // Traverse all adjacent vertices of this vertex\n        if let Some(adj_vets) = graph.adj_list.get(&vet) {\n            for &adj_vet in adj_vets {\n                if visited.contains(&adj_vet) {\n                    continue; // Skip vertices that have been visited\n                }\n                que.push_back(adj_vet); // Only enqueue unvisited vertices\n                visited.insert(adj_vet); // Mark this vertex as visited\n            }\n        }\n    }\n    // Return vertex traversal sequence\n    res\n}\n
graph_bfs.c
/* Node queue structure */\ntypedef struct {\n    Vertex *vertices[MAX_SIZE];\n    int front, rear, size;\n} Queue;\n\n/* Constructor */\nQueue *newQueue() {\n    Queue *q = (Queue *)malloc(sizeof(Queue));\n    q->front = q->rear = q->size = 0;\n    return q;\n}\n\n/* Check if the queue is empty */\nint isEmpty(Queue *q) {\n    return q->size == 0;\n}\n\n/* Enqueue operation */\nvoid enqueue(Queue *q, Vertex *vet) {\n    q->vertices[q->rear] = vet;\n    q->rear = (q->rear + 1) % MAX_SIZE;\n    q->size++;\n}\n\n/* Dequeue operation */\nVertex *dequeue(Queue *q) {\n    Vertex *vet = q->vertices[q->front];\n    q->front = (q->front + 1) % MAX_SIZE;\n    q->size--;\n    return vet;\n}\n\n/* Check if vertex has been visited */\nint isVisited(Vertex **visited, int size, Vertex *vet) {\n    // Traverse to find node using O(n) time\n    for (int i = 0; i < size; i++) {\n        if (visited[i] == vet)\n            return 1;\n    }\n    return 0;\n}\n\n/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nvoid graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) {\n    // Queue used to implement BFS\n    Queue *queue = newQueue();\n    enqueue(queue, startVet);\n    visited[(*visitedSize)++] = startVet;\n    // Starting from vertex vet, loop until all vertices are visited\n    while (!isEmpty(queue)) {\n        Vertex *vet = dequeue(queue); // Dequeue the front vertex\n        res[(*resSize)++] = vet;      // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        AdjListNode *node = findNode(graph, vet);\n        while (node != NULL) {\n            // Skip vertices that have been visited\n            if (!isVisited(visited, *visitedSize, node->vertex)) {\n                enqueue(queue, node->vertex);             // Only enqueue unvisited vertices\n                visited[(*visitedSize)++] = node->vertex; // Mark this vertex as visited\n            }\n            node = node->next;\n        }\n    }\n    // Free memory\n    free(queue);\n}\n
graph_bfs.kt
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList<Vertex?> {\n    // Vertex traversal sequence\n    val res = mutableListOf<Vertex?>()\n    // Hash set for recording vertices that have been visited\n    val visited = HashSet<Vertex>()\n    visited.add(startVet)\n    // Queue used to implement BFS\n    val que = LinkedList<Vertex>()\n    que.offer(startVet)\n    // Starting from vertex vet, loop until all vertices are visited\n    while (!que.isEmpty()) {\n        val vet = que.poll() // Dequeue the front vertex\n        res.add(vet)         // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for (adjVet in graph.adjList[vet]!!) {\n            if (visited.contains(adjVet))\n                continue        // Skip vertices that have been visited\n            que.offer(adjVet)   // Only enqueue unvisited vertices\n            visited.add(adjVet) // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res\n}\n
graph_bfs.rb
### Breadth-first traversal ###\ndef graph_bfs(graph, start_vet)\n  # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\n  # Vertex traversal sequence\n  res = []\n  # Hash set for recording vertices that have been visited\n  visited = Set.new([start_vet])\n  # Queue used to implement BFS\n  que = [start_vet]\n  # Starting from vertex vet, loop until all vertices are visited\n  while que.length > 0\n    vet = que.shift # Dequeue the front vertex\n    res << vet # Record visited vertex\n    # Traverse all adjacent vertices of this vertex\n    for adj_vet in graph.adj_list[vet]\n      next if visited.include?(adj_vet) # Skip vertices that have been visited\n      que << adj_vet # Only enqueue unvisited vertices\n      visited.add(adj_vet) # Mark this vertex as visited\n    end\n  end\n  # Return vertex traversal sequence\n  res\nend\n

The code is relatively abstract; it is recommended to refer to Figure 9-10 to deepen understanding.

<1><2><3><4><5><6><7><8><9><10><11>

Figure 9-10   Steps of breadth-first search of a graph

Is the breadth-first traversal sequence unique?

Not unique. Breadth-first search only requires traversing in a \"near to far\" order, and the traversal order of vertices at the same distance can be arbitrarily shuffled. Taking Figure 9-10 as an example, the visit order of vertices \\(1\\) and \\(3\\) can be swapped, as can the visit order of vertices \\(2\\), \\(4\\), and \\(6\\).

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#2-complexity-analysis","level":3,"title":"2.   Complexity Analysis","text":"

Time complexity: All vertices will be enqueued and dequeued once, using \\(O(|V|)\\) time; in the process of traversing adjacent vertices, since it is an undirected graph, all edges will be visited \\(2\\) times, using \\(O(2|E|)\\) time; overall using \\(O(|V| + |E|)\\) time.

Space complexity: The list res, hash set visited, and queue que can contain at most \\(|V|\\) vertices, using \\(O(|V|)\\) space.

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#932-depth-first-search","level":2,"title":"9.3.2   Depth-First Search","text":"

Depth-first search is a traversal method that prioritizes going as far as possible, then backtracks when no path remains. As shown in Figure 9-11, starting from the top-left vertex, visit an adjacent vertex of the current vertex, continuing until reaching a dead end, then return and continue going as far as possible before returning again, and so on, until all vertices have been traversed.

Figure 9-11   Depth-first search of a graph

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#1-algorithm-implementation_1","level":3,"title":"1.   Algorithm Implementation","text":"

This \"go as far as possible then return\" algorithm paradigm is typically implemented using recursion. Similar to breadth-first search, in depth-first search we also need a hash set visited to record visited vertices and avoid revisiting.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_dfs.py
def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex):\n    \"\"\"Depth-first traversal helper function\"\"\"\n    res.append(vet)  # Record visited vertex\n    visited.add(vet)  # Mark this vertex as visited\n    # Traverse all adjacent vertices of this vertex\n    for adjVet in graph.adj_list[vet]:\n        if adjVet in visited:\n            continue  # Skip vertices that have been visited\n        # Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet)\n\ndef graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:\n    \"\"\"Depth-first traversal\"\"\"\n    # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\n    # Vertex traversal sequence\n    res = []\n    # Hash set for recording vertices that have been visited\n    visited = set[Vertex]()\n    dfs(graph, visited, res, start_vet)\n    return res\n
graph_dfs.cpp
/* Depth-first traversal helper function */\nvoid dfs(GraphAdjList &graph, unordered_set<Vertex *> &visited, vector<Vertex *> &res, Vertex *vet) {\n    res.push_back(vet);   // Record visited vertex\n    visited.emplace(vet); // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for (Vertex *adjVet : graph.adjList[vet]) {\n        if (visited.count(adjVet))\n            continue; // Skip vertices that have been visited\n        // Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nvector<Vertex *> graphDFS(GraphAdjList &graph, Vertex *startVet) {\n    // Vertex traversal sequence\n    vector<Vertex *> res;\n    // Hash set for recording vertices that have been visited\n    unordered_set<Vertex *> visited;\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
graph_dfs.java
/* Depth-first traversal helper function */\nvoid dfs(GraphAdjList graph, Set<Vertex> visited, List<Vertex> res, Vertex vet) {\n    res.add(vet);     // Record visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for (Vertex adjVet : graph.adjList.get(vet)) {\n        if (visited.contains(adjVet))\n            continue; // Skip vertices that have been visited\n        // Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nList<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = new ArrayList<>();\n    // Hash set for recording vertices that have been visited\n    Set<Vertex> visited = new HashSet<>();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
graph_dfs.cs
/* Depth-first traversal helper function */\nvoid DFS(GraphAdjList graph, HashSet<Vertex> visited, List<Vertex> res, Vertex vet) {\n    res.Add(vet);     // Record visited vertex\n    visited.Add(vet); // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    foreach (Vertex adjVet in graph.adjList[vet]) {\n        if (visited.Contains(adjVet)) {\n            continue; // Skip vertices that have been visited\n        }\n        // Recursively visit adjacent vertices\n        DFS(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nList<Vertex> GraphDFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = [];\n    // Hash set for recording vertices that have been visited\n    HashSet<Vertex> visited = [];\n    DFS(graph, visited, res, startVet);\n    return res;\n}\n
graph_dfs.go
/* Depth-first traversal helper function */\nfunc dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) {\n    // append operation returns a new reference, must reassign original reference to new slice's reference\n    *res = append(*res, vet)\n    visited[vet] = struct{}{}\n    // Traverse all adjacent vertices of this vertex\n    for _, adjVet := range g.adjList[vet] {\n        _, isExist := visited[adjVet]\n        // Recursively visit adjacent vertices\n        if !isExist {\n            dfs(g, visited, res, adjVet)\n        }\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunc graphDFS(g *graphAdjList, startVet Vertex) []Vertex {\n    // Vertex traversal sequence\n    res := make([]Vertex, 0)\n    // Hash set for recording vertices that have been visited\n    visited := make(map[Vertex]struct{})\n    dfs(g, visited, &res, startVet)\n    // Return vertex traversal sequence\n    return res\n}\n
graph_dfs.swift
/* Depth-first traversal helper function */\nfunc dfs(graph: GraphAdjList, visited: inout Set<Vertex>, res: inout [Vertex], vet: Vertex) {\n    res.append(vet) // Record visited vertex\n    visited.insert(vet) // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for adjVet in graph.adjList[vet] ?? [] {\n        if visited.contains(adjVet) {\n            continue // Skip vertices that have been visited\n        }\n        // Recursively visit adjacent vertices\n        dfs(graph: graph, visited: &visited, res: &res, vet: adjVet)\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunc graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {\n    // Vertex traversal sequence\n    var res: [Vertex] = []\n    // Hash set for recording vertices that have been visited\n    var visited: Set<Vertex> = []\n    dfs(graph: graph, visited: &visited, res: &res, vet: startVet)\n    return res\n}\n
graph_dfs.js
/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunction dfs(graph, visited, res, vet) {\n    res.push(vet); // Record visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for (const adjVet of graph.adjList.get(vet)) {\n        if (visited.has(adjVet)) {\n            continue; // Skip vertices that have been visited\n        }\n        // Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunction graphDFS(graph, startVet) {\n    // Vertex traversal sequence\n    const res = [];\n    // Hash set for recording vertices that have been visited\n    const visited = new Set();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
graph_dfs.ts
/* Depth-first traversal helper function */\nfunction dfs(\n    graph: GraphAdjList,\n    visited: Set<Vertex>,\n    res: Vertex[],\n    vet: Vertex\n): void {\n    res.push(vet); // Record visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for (const adjVet of graph.adjList.get(vet)) {\n        if (visited.has(adjVet)) {\n            continue; // Skip vertices that have been visited\n        }\n        // Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunction graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {\n    // Vertex traversal sequence\n    const res: Vertex[] = [];\n    // Hash set for recording vertices that have been visited\n    const visited: Set<Vertex> = new Set();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
graph_dfs.dart
/* Depth-first traversal helper function */\nvoid dfs(\n  GraphAdjList graph,\n  Set<Vertex> visited,\n  List<Vertex> res,\n  Vertex vet,\n) {\n  res.add(vet); // Record visited vertex\n  visited.add(vet); // Mark this vertex as visited\n  // Traverse all adjacent vertices of this vertex\n  for (Vertex adjVet in graph.adjList[vet]!) {\n    if (visited.contains(adjVet)) {\n      continue; // Skip vertices that have been visited\n    }\n    // Recursively visit adjacent vertices\n    dfs(graph, visited, res, adjVet);\n  }\n}\n\n/* Depth-first traversal */\nList<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {\n  // Vertex traversal sequence\n  List<Vertex> res = [];\n  // Hash set for recording vertices that have been visited\n  Set<Vertex> visited = {};\n  dfs(graph, visited, res, startVet);\n  return res;\n}\n
graph_dfs.rs
/* Depth-first traversal helper function */\nfn dfs(graph: &GraphAdjList, visited: &mut HashSet<Vertex>, res: &mut Vec<Vertex>, vet: Vertex) {\n    res.push(vet); // Record visited vertex\n    visited.insert(vet); // Mark this vertex as visited\n                         // Traverse all adjacent vertices of this vertex\n    if let Some(adj_vets) = graph.adj_list.get(&vet) {\n        for &adj_vet in adj_vets {\n            if visited.contains(&adj_vet) {\n                continue; // Skip vertices that have been visited\n            }\n            // Recursively visit adjacent vertices\n            dfs(graph, visited, res, adj_vet);\n        }\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {\n    // Vertex traversal sequence\n    let mut res = vec![];\n    // Hash set for recording vertices that have been visited\n    let mut visited = HashSet::new();\n    dfs(&graph, &mut visited, &mut res, start_vet);\n\n    res\n}\n
graph_dfs.c
/* Check if vertex has been visited */\nint isVisited(Vertex **res, int size, Vertex *vet) {\n    // Traverse to find node using O(n) time\n    for (int i = 0; i < size; i++) {\n        if (res[i] == vet) {\n            return 1;\n        }\n    }\n    return 0;\n}\n\n/* Depth-first traversal helper function */\nvoid dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) {\n    // Record visited vertex\n    res[(*resSize)++] = vet;\n    // Traverse all adjacent vertices of this vertex\n    AdjListNode *node = findNode(graph, vet);\n    while (node != NULL) {\n        // Skip vertices that have been visited\n        if (!isVisited(res, *resSize, node->vertex)) {\n            // Recursively visit adjacent vertices\n            dfs(graph, res, resSize, node->vertex);\n        }\n        node = node->next;\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nvoid graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) {\n    dfs(graph, res, resSize, startVet);\n}\n
graph_dfs.kt
/* Depth-first traversal helper function */\nfun dfs(\n    graph: GraphAdjList,\n    visited: MutableSet<Vertex?>,\n    res: MutableList<Vertex?>,\n    vet: Vertex?\n) {\n    res.add(vet)     // Record visited vertex\n    visited.add(vet) // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for (adjVet in graph.adjList[vet]!!) {\n        if (visited.contains(adjVet))\n            continue  // Skip vertices that have been visited\n        // Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet)\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList<Vertex?> {\n    // Vertex traversal sequence\n    val res = mutableListOf<Vertex?>()\n    // Hash set for recording vertices that have been visited\n    val visited = HashSet<Vertex?>()\n    dfs(graph, visited, res, startVet)\n    return res\n}\n
graph_dfs.rb
### Depth-first traversal helper function ###\ndef dfs(graph, visited, res, vet)\n  res << vet # Record visited vertex\n  visited.add(vet) # Mark this vertex as visited\n  # Traverse all adjacent vertices of this vertex\n  for adj_vet in graph.adj_list[vet]\n    next if visited.include?(adj_vet) # Skip vertices that have been visited\n    # Recursively visit adjacent vertices\n    dfs(graph, visited, res, adj_vet)\n  end\nend\n\n### Depth-first traversal ###\ndef graph_dfs(graph, start_vet)\n  # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\n  # Vertex traversal sequence\n  res = []\n  # Hash set for recording vertices that have been visited\n  visited = Set.new\n  dfs(graph, visited, res, start_vet)\n  res\nend\n

The algorithm flow of depth-first search is shown in Figure 9-12.

  • Straight dashed lines represent downward recursion, indicating that a new recursive method has been initiated to visit a new vertex.
  • Curved dashed lines represent upward backtracking, indicating that this recursive method has returned to the position where it was initiated.

To deepen understanding, it is recommended to combine Figure 9-12 with the code to mentally simulate (or draw out) the entire DFS process, including when each recursive method is initiated and when it returns.

<1><2><3><4><5><6><7><8><9><10><11>

Figure 9-12   Steps of depth-first search of a graph

Is the depth-first traversal sequence unique?

Similar to breadth-first search, the order of depth-first traversal sequences is also not unique. Given a certain vertex, exploring in any direction first is valid, meaning the order of adjacent vertices can be arbitrarily shuffled, all being depth-first search.

Taking tree traversal as an example, \"root \\(\\rightarrow\\) left \\(\\rightarrow\\) right\", \"left \\(\\rightarrow\\) root \\(\\rightarrow\\) right\", and \"left \\(\\rightarrow\\) right \\(\\rightarrow\\) root\" correspond to pre-order, in-order, and post-order traversals, respectively. They represent three different traversal priorities, yet all three belong to depth-first search.

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#2-complexity-analysis_1","level":3,"title":"2.   Complexity Analysis","text":"

Time complexity: All vertices will be visited \\(1\\) time, using \\(O(|V|)\\) time; all edges will be visited \\(2\\) times, using \\(O(2|E|)\\) time; overall using \\(O(|V| + |E|)\\) time.

Space complexity: The list res and hash set visited can contain at most \\(|V|\\) vertices, and the maximum recursion depth is \\(|V|\\), therefore using \\(O(|V|)\\) space.

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/summary/","level":1,"title":"9.4   Summary","text":"","path":["Chapter 9. Graph","9.4   Summary"],"tags":[]},{"location":"chapter_graph/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Graphs consist of vertices and edges and can be represented as a set of vertices and a set of edges.
  • Compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) have a higher degree of freedom and are therefore more complex.
  • Directed graphs have edges with directionality, connected graphs have all vertices reachable from any vertex, and weighted graphs have edges that each contain a weight variable.
  • Adjacency matrices use matrices to represent graphs, where each row (column) represents a vertex, and matrix elements represent edges, using \\(1\\) or \\(0\\) to indicate whether two vertices have an edge or not. Adjacency matrices are highly efficient for addition, deletion, lookup, and modification operations, but consume significant space.
  • Adjacency lists use multiple linked lists to represent graphs, where the \\(i\\)-th linked list corresponds to vertex \\(i\\) and stores all adjacent vertices of that vertex. Adjacency lists are more space-efficient than adjacency matrices, but have lower time efficiency because they require traversing linked lists to find edges.
  • When linked lists in adjacency lists become too long, they can be converted to red-black trees or hash tables, thereby improving lookup efficiency.
  • From an algorithmic perspective, adjacency matrices embody \"trading space for time\", while adjacency lists embody \"trading time for space\".
  • Graphs can be used to model various real-world systems, such as social networks and subway lines.
  • Trees are a special case of graphs, and tree traversal is a special case of graph traversal.
  • Breadth-first search of graphs is a near-to-far, layer-by-layer expansion search method, typically implemented using a queue.
  • Depth-first search of graphs is a search method that prioritizes going as far as possible and backtracks when no path remains, commonly implemented using recursion.
","path":["Chapter 9. Graph","9.4   Summary"],"tags":[]},{"location":"chapter_graph/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Is a path defined as a sequence of vertices or a sequence of edges?

The definitions in different language versions of Wikipedia are inconsistent: the English version states \"a path is a sequence of edges\", while the Chinese version states \"a path is a sequence of vertices\". The following is the original English text: In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices.

In this text, a path is viewed as a sequence of edges, not a sequence of vertices. This is because there may be multiple edges connecting two vertices, in which case each edge corresponds to a path.

Q: In a disconnected graph, will there be unreachable vertices?

In a disconnected graph, starting from a certain vertex, at least one vertex cannot be reached. Traversing a disconnected graph requires setting multiple starting points to traverse all connected components of the graph.

Q: In an adjacency list, is there a requirement for the order of \"all vertices connected to that vertex\"?

It can be in any order. However, in practical applications, it may be necessary to sort according to specified rules, such as the order in which vertices were added, or the order of vertex values, which helps quickly find vertices \"with certain extreme values\".

","path":["Chapter 9. Graph","9.4   Summary"],"tags":[]},{"location":"chapter_greedy/","level":1,"title":"Chapter 15.   Greedy","text":"

Abstract

Sunflowers turn toward the sun, constantly pursuing the maximum potential for their own growth.

Through rounds of simple choices, greedy strategies gradually lead to the best answer.

","path":["Chapter 15. Greedy","Chapter 15.   Greedy"],"tags":[]},{"location":"chapter_greedy/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 15.1   Greedy Algorithm
  • 15.2   Fractional Knapsack Problem
  • 15.3   Maximum Capacity Problem
  • 15.4   Maximum Product Cutting Problem
  • 15.5   Summary
","path":["Chapter 15. Greedy","Chapter 15.   Greedy"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/","level":1,"title":"15.2   Fractional Knapsack Problem","text":"

Question

Given \\(n\\) items, where the weight of the \\(i\\)-th item is \\(wgt[i-1]\\) and its value is \\(val[i-1]\\), and a knapsack with capacity \\(cap\\). Each item can be selected only once, but a portion of an item can be selected, with the value calculated based on the proportion of weight selected, what is the maximum value of items in the knapsack under the limited capacity? An example is shown in Figure 15-3.

Figure 15-3   Example data for the fractional knapsack problem

The fractional knapsack problem is very similar overall to the 0-1 knapsack problem, with states including the current item \\(i\\) and capacity \\(c\\), and the goal being to maximize value under the limited knapsack capacity.

The difference is that this problem allows selecting only a portion of an item. As shown in Figure 15-4, we can arbitrarily split items and calculate the corresponding value based on the weight proportion.

  1. For item \\(i\\), its value per unit weight is \\(val[i-1] / wgt[i-1]\\), referred to as unit value.
  2. Suppose we put a portion of item \\(i\\) with weight \\(w\\) into the knapsack, then the value added to the knapsack is \\(w \\times val[i-1] / wgt[i-1]\\).

Figure 15-4   Value of items per unit weight

","path":["Chapter 15. Greedy","15.2   Fractional Knapsack Problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#1-greedy-strategy-determination","level":3,"title":"1.   Greedy Strategy Determination","text":"

Maximizing the total value of items in the knapsack is essentially maximizing the value per unit weight of items. From this, we can derive the greedy strategy shown in Figure 15-5.

  1. Sort items by unit value from high to low.
  2. Iterate through all items, greedily selecting the item with the highest unit value in each round.
  3. If the remaining knapsack capacity is insufficient, use a portion of the current item to fill the knapsack.

Figure 15-5   Greedy strategy for the fractional knapsack problem

","path":["Chapter 15. Greedy","15.2   Fractional Knapsack Problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"

We created an Item class to facilitate sorting items by unit value. We loop to make greedy selections, breaking when the knapsack is full and returning the solution:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby fractional_knapsack.py
class Item:\n    \"\"\"Item\"\"\"\n\n    def __init__(self, w: int, v: int):\n        self.w = w  # Item weight\n        self.v = v  # Item value\n\ndef fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Fractional knapsack: Greedy algorithm\"\"\"\n    # Create item list with two attributes: weight, value\n    items = [Item(w, v) for w, v in zip(wgt, val)]\n    # Sort by unit value item.v / item.w from high to low\n    items.sort(key=lambda item: item.v / item.w, reverse=True)\n    # Loop for greedy selection\n    res = 0\n    for item in items:\n        if item.w <= cap:\n            # If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v\n            cap -= item.w\n        else:\n            # If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (item.v / item.w) * cap\n            # No remaining capacity, so break out of the loop\n            break\n    return res\n
fractional_knapsack.cpp
/* Item */\nclass Item {\n  public:\n    int w; // Item weight\n    int v; // Item value\n\n    Item(int w, int v) : w(w), v(v) {\n    }\n};\n\n/* Fractional knapsack: Greedy algorithm */\ndouble fractionalKnapsack(vector<int> &wgt, vector<int> &val, int cap) {\n    // Create item list with two attributes: weight, value\n    vector<Item> items;\n    for (int i = 0; i < wgt.size(); i++) {\n        items.push_back(Item(wgt[i], val[i]));\n    }\n    // Sort by unit value item.v / item.w from high to low\n    sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; });\n    // Loop for greedy selection\n    double res = 0;\n    for (auto &item : items) {\n        if (item.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (double)item.v / item.w * cap;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    return res;\n}\n
fractional_knapsack.java
/* Item */\nclass Item {\n    int w; // Item weight\n    int v; // Item value\n\n    public Item(int w, int v) {\n        this.w = w;\n        this.v = v;\n    }\n}\n\n/* Fractional knapsack: Greedy algorithm */\ndouble fractionalKnapsack(int[] wgt, int[] val, int cap) {\n    // Create item list with two attributes: weight, value\n    Item[] items = new Item[wgt.length];\n    for (int i = 0; i < wgt.length; i++) {\n        items[i] = new Item(wgt[i], val[i]);\n    }\n    // Sort by unit value item.v / item.w from high to low\n    Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w)));\n    // Loop for greedy selection\n    double res = 0;\n    for (Item item : items) {\n        if (item.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (double) item.v / item.w * cap;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    return res;\n}\n
fractional_knapsack.cs
/* Item */\nclass Item(int w, int v) {\n    public int w = w; // Item weight\n    public int v = v; // Item value\n}\n\n/* Fractional knapsack: Greedy algorithm */\ndouble FractionalKnapsack(int[] wgt, int[] val, int cap) {\n    // Create item list with two attributes: weight, value\n    Item[] items = new Item[wgt.Length];\n    for (int i = 0; i < wgt.Length; i++) {\n        items[i] = new Item(wgt[i], val[i]);\n    }\n    // Sort by unit value item.v / item.w from high to low\n    Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w));\n    // Loop for greedy selection\n    double res = 0;\n    foreach (Item item in items) {\n        if (item.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (double)item.v / item.w * cap;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    return res;\n}\n
fractional_knapsack.go
/* Item */\ntype Item struct {\n    w int // Item weight\n    v int // Item value\n}\n\n/* Fractional knapsack: Greedy algorithm */\nfunc fractionalKnapsack(wgt []int, val []int, cap int) float64 {\n    // Create item list with two attributes: weight, value\n    items := make([]Item, len(wgt))\n    for i := 0; i < len(wgt); i++ {\n        items[i] = Item{wgt[i], val[i]}\n    }\n    // Sort by unit value item.v / item.w from high to low\n    sort.Slice(items, func(i, j int) bool {\n        return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w)\n    })\n    // Loop for greedy selection\n    res := 0.0\n    for _, item := range items {\n        if item.w <= cap {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += float64(item.v)\n            cap -= item.w\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += float64(item.v) / float64(item.w) * float64(cap)\n            // No remaining capacity, so break out of the loop\n            break\n        }\n    }\n    return res\n}\n
fractional_knapsack.swift
/* Item */\nclass Item {\n    var w: Int // Item weight\n    var v: Int // Item value\n\n    init(w: Int, v: Int) {\n        self.w = w\n        self.v = v\n    }\n}\n\n/* Fractional knapsack: Greedy algorithm */\nfunc fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double {\n    // Create item list with two attributes: weight, value\n    var items = zip(wgt, val).map { Item(w: $0, v: $1) }\n    // Sort by unit value item.v / item.w from high to low\n    items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) }\n    // Loop for greedy selection\n    var res = 0.0\n    var cap = cap\n    for item in items {\n        if item.w <= cap {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += Double(item.v)\n            cap -= item.w\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += Double(item.v) / Double(item.w) * Double(cap)\n            // No remaining capacity, so break out of the loop\n            break\n        }\n    }\n    return res\n}\n
fractional_knapsack.js
/* Item */\nclass Item {\n    constructor(w, v) {\n        this.w = w; // Item weight\n        this.v = v; // Item value\n    }\n}\n\n/* Fractional knapsack: Greedy algorithm */\nfunction fractionalKnapsack(wgt, val, cap) {\n    // Create item list with two attributes: weight, value\n    const items = wgt.map((w, i) => new Item(w, val[i]));\n    // Sort by unit value item.v / item.w from high to low\n    items.sort((a, b) => b.v / b.w - a.v / a.w);\n    // Loop for greedy selection\n    let res = 0;\n    for (const item of items) {\n        if (item.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (item.v / item.w) * cap;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    return res;\n}\n
fractional_knapsack.ts
/* Item */\nclass Item {\n    w: number; // Item weight\n    v: number; // Item value\n\n    constructor(w: number, v: number) {\n        this.w = w;\n        this.v = v;\n    }\n}\n\n/* Fractional knapsack: Greedy algorithm */\nfunction fractionalKnapsack(wgt: number[], val: number[], cap: number): number {\n    // Create item list with two attributes: weight, value\n    const items: Item[] = wgt.map((w, i) => new Item(w, val[i]));\n    // Sort by unit value item.v / item.w from high to low\n    items.sort((a, b) => b.v / b.w - a.v / a.w);\n    // Loop for greedy selection\n    let res = 0;\n    for (const item of items) {\n        if (item.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (item.v / item.w) * cap;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    return res;\n}\n
fractional_knapsack.dart
/* Item */\nclass Item {\n  int w; // Item weight\n  int v; // Item value\n\n  Item(this.w, this.v);\n}\n\n/* Fractional knapsack: Greedy algorithm */\ndouble fractionalKnapsack(List<int> wgt, List<int> val, int cap) {\n  // Create item list with two attributes: weight, value\n  List<Item> items = List.generate(wgt.length, (i) => Item(wgt[i], val[i]));\n  // Sort by unit value item.v / item.w from high to low\n  items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w));\n  // Loop for greedy selection\n  double res = 0;\n  for (Item item in items) {\n    if (item.w <= cap) {\n      // If remaining capacity is sufficient, put the entire current item into the knapsack\n      res += item.v;\n      cap -= item.w;\n    } else {\n      // If remaining capacity is insufficient, put part of the current item into the knapsack\n      res += item.v / item.w * cap;\n      // No remaining capacity, so break out of the loop\n      break;\n    }\n  }\n  return res;\n}\n
fractional_knapsack.rs
/* Item */\nstruct Item {\n    w: i32, // Item weight\n    v: i32, // Item value\n}\n\nimpl Item {\n    fn new(w: i32, v: i32) -> Self {\n        Self { w, v }\n    }\n}\n\n/* Fractional knapsack: Greedy algorithm */\nfn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 {\n    // Create item list with two attributes: weight, value\n    let mut items = wgt\n        .iter()\n        .zip(val.iter())\n        .map(|(&w, &v)| Item::new(w, v))\n        .collect::<Vec<Item>>();\n    // Sort by unit value item.v / item.w from high to low\n    items.sort_by(|a, b| {\n        (b.v as f64 / b.w as f64)\n            .partial_cmp(&(a.v as f64 / a.w as f64))\n            .unwrap()\n    });\n    // Loop for greedy selection\n    let mut res = 0.0;\n    for item in &items {\n        if item.w <= cap {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v as f64;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += item.v as f64 / item.w as f64 * cap as f64;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    res\n}\n
fractional_knapsack.c
/* Item */\ntypedef struct {\n    int w; // Item weight\n    int v; // Item value\n} Item;\n\n/* Fractional knapsack: Greedy algorithm */\nfloat fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) {\n    // Create item list with two attributes: weight, value\n    Item *items = malloc(sizeof(Item) * itemCount);\n    for (int i = 0; i < itemCount; i++) {\n        items[i] = (Item){.w = wgt[i], .v = val[i]};\n    }\n    // Sort by unit value item.v / item.w from high to low\n    qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity);\n    // Loop for greedy selection\n    float res = 0.0;\n    for (int i = 0; i < itemCount; i++) {\n        if (items[i].w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += items[i].v;\n            cap -= items[i].w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (float)cap / items[i].w * items[i].v;\n            cap = 0;\n            break;\n        }\n    }\n    free(items);\n    return res;\n}\n
fractional_knapsack.kt
/* Item */\nclass Item(\n    val w: Int, // Item\n    val v: Int  // Item value\n)\n\n/* Fractional knapsack: Greedy algorithm */\nfun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double {\n    // Create item list with two attributes: weight, value\n    var cap = c\n    val items = arrayOfNulls<Item>(wgt.size)\n    for (i in wgt.indices) {\n        items[i] = Item(wgt[i], _val[i])\n    }\n    // Sort by unit value item.v / item.w from high to low\n    items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) }\n    // Loop for greedy selection\n    var res = 0.0\n    for (item in items) {\n        if (item!!.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v\n            cap -= item.w\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += item.v.toDouble() / item.w * cap\n            // No remaining capacity, so break out of the loop\n            break\n        }\n    }\n    return res\n}\n
fractional_knapsack.rb
### Item ###\nclass Item\n  attr_accessor :w # Item weight\n  attr_accessor :v # Item value\n\n  def initialize(w, v)\n    @w = w\n    @v = v\n  end\nend\n\n### Fractional knapsack: greedy ###\ndef fractional_knapsack(wgt, val, cap)\n  # Create item list with two attributes: weight, value\n  items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) }\n  # Sort by unit value item.v / item.w from high to low\n  items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) }\n  # Loop for greedy selection\n  res = 0\n  for item in items\n    if item.w <= cap\n      # If remaining capacity is sufficient, put the entire current item into the knapsack\n      res += item.v\n      cap -= item.w\n    else\n      # If remaining capacity is insufficient, put part of the current item into the knapsack\n      res += (item.v.to_f / item.w) * cap\n      # No remaining capacity, so break out of the loop\n      break\n    end\n  end\n  res\nend\n

The time complexity of built-in sorting algorithms is usually \\(O(\\log n)\\), and the space complexity is usually \\(O(\\log n)\\) or \\(O(n)\\), depending on the specific implementation of the programming language.

Apart from sorting, in the worst case the entire item list needs to be traversed, therefore the time complexity is \\(O(n)\\), where \\(n\\) is the number of items.

Since an Item object list is initialized, the space complexity is \\(O(n)\\).

","path":["Chapter 15. Greedy","15.2   Fractional Knapsack Problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#3-correctness-proof","level":3,"title":"3.   Correctness Proof","text":"

Using proof by contradiction. Suppose item \\(x\\) has the highest unit value, and some algorithm yields a maximum value of res, but this solution does not include item \\(x\\).

Now remove a unit weight of any item from the knapsack and replace it with a unit weight of item \\(x\\). Since item \\(x\\) has the highest unit value, the total value after replacement will definitely be greater than res. This contradicts the assumption that res is the optimal solution, proving that the optimal solution must include item \\(x\\).

For other items in this solution, we can also construct the above contradiction. In summary, items with greater unit value are always better choices, which proves that the greedy strategy is effective.

As shown in Figure 15-6, if we view item weight and item unit value as the horizontal and vertical axes of a two-dimensional chart respectively, then the fractional knapsack problem can be transformed into \"finding the maximum area enclosed within a limited horizontal axis range\". This analogy can help us understand the effectiveness of the greedy strategy from a geometric perspective.

Figure 15-6   Geometric representation of the fractional knapsack problem

","path":["Chapter 15. Greedy","15.2   Fractional Knapsack Problem"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/","level":1,"title":"15.1   Greedy Algorithm","text":"

Greedy algorithm is a common algorithm for solving optimization problems. Its basic idea is to make the seemingly best choice at each decision stage of the problem, that is, to greedily make locally optimal decisions in hopes of obtaining a globally optimal solution. Greedy algorithms are simple and efficient, and are widely applied in many practical problems.

Greedy algorithms and dynamic programming are both commonly used to solve optimization problems. They share some similarities, such as both relying on the optimal substructure property, but they work differently.

  • Dynamic programming considers all previous decisions when making the current decision, and uses solutions to past subproblems to construct the solution to the current subproblem.
  • Greedy algorithms do not consider past decisions, but instead make greedy choices moving forward, continually reducing the problem size until the problem is solved.

We will first understand how greedy algorithms work through the example problem \"coin change\". This problem has already been introduced in the \"Complete Knapsack Problem\" chapter, so I believe you are not unfamiliar with it.

Question

Given \\(n\\) types of coins, where the denomination of the \\(i\\)-th type of coin is \\(coins[i - 1]\\), and the target amount is \\(amt\\), with each type of coin available for repeated selection, what is the minimum number of coins needed to make up the target amount? If it is impossible to make up the target amount, return \\(-1\\).

The greedy strategy adopted for this problem is shown in Figure 15-1. Given a target amount, we greedily select the coin that is not greater than and closest to it, and continuously repeat this step until the target amount is reached.

Figure 15-1   Greedy strategy for coin change

The implementation code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_greedy.py
def coin_change_greedy(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: Greedy algorithm\"\"\"\n    # Assume coins list is sorted\n    i = len(coins) - 1\n    count = 0\n    # Loop to make greedy choices until no remaining amount\n    while amt > 0:\n        # Find the coin that is less than and closest to the remaining amount\n        while i > 0 and coins[i] > amt:\n            i -= 1\n        # Choose coins[i]\n        amt -= coins[i]\n        count += 1\n    # If no feasible solution is found, return -1\n    return count if amt == 0 else -1\n
coin_change_greedy.cpp
/* Coin change: Greedy algorithm */\nint coinChangeGreedy(vector<int> &coins, int amt) {\n    // Assume coins list is sorted\n    int i = coins.size() - 1;\n    int count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt == 0 ? count : -1;\n}\n
coin_change_greedy.java
/* Coin change: Greedy algorithm */\nint coinChangeGreedy(int[] coins, int amt) {\n    // Assume coins list is sorted\n    int i = coins.length - 1;\n    int count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt == 0 ? count : -1;\n}\n
coin_change_greedy.cs
/* Coin change: Greedy algorithm */\nint CoinChangeGreedy(int[] coins, int amt) {\n    // Assume coins list is sorted\n    int i = coins.Length - 1;\n    int count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt == 0 ? count : -1;\n}\n
coin_change_greedy.go
/* Coin change: Greedy algorithm */\nfunc coinChangeGreedy(coins []int, amt int) int {\n    // Assume coins list is sorted\n    i := len(coins) - 1\n    count := 0\n    // Loop to make greedy choices until no remaining amount\n    for amt > 0 {\n        // Find the coin that is less than and closest to the remaining amount\n        for i > 0 && coins[i] > amt {\n            i--\n        }\n        // Choose coins[i]\n        amt -= coins[i]\n        count++\n    }\n    // If no feasible solution is found, return -1\n    if amt != 0 {\n        return -1\n    }\n    return count\n}\n
coin_change_greedy.swift
/* Coin change: Greedy algorithm */\nfunc coinChangeGreedy(coins: [Int], amt: Int) -> Int {\n    // Assume coins list is sorted\n    var i = coins.count - 1\n    var count = 0\n    var amt = amt\n    // Loop to make greedy choices until no remaining amount\n    while amt > 0 {\n        // Find the coin that is less than and closest to the remaining amount\n        while i > 0 && coins[i] > amt {\n            i -= 1\n        }\n        // Choose coins[i]\n        amt -= coins[i]\n        count += 1\n    }\n    // If no feasible solution is found, return -1\n    return amt == 0 ? count : -1\n}\n
coin_change_greedy.js
/* Coin change: Greedy algorithm */\nfunction coinChangeGreedy(coins, amt) {\n    // Assume coins array is sorted\n    let i = coins.length - 1;\n    let count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt === 0 ? count : -1;\n}\n
coin_change_greedy.ts
/* Coin change: Greedy algorithm */\nfunction coinChangeGreedy(coins: number[], amt: number): number {\n    // Assume coins array is sorted\n    let i = coins.length - 1;\n    let count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt === 0 ? count : -1;\n}\n
coin_change_greedy.dart
/* Coin change: Greedy algorithm */\nint coinChangeGreedy(List<int> coins, int amt) {\n  // Assume coins list is sorted\n  int i = coins.length - 1;\n  int count = 0;\n  // Loop to make greedy choices until no remaining amount\n  while (amt > 0) {\n    // Find the coin that is less than and closest to the remaining amount\n    while (i > 0 && coins[i] > amt) {\n      i--;\n    }\n    // Choose coins[i]\n    amt -= coins[i];\n    count++;\n  }\n  // If no feasible solution is found, return -1\n  return amt == 0 ? count : -1;\n}\n
coin_change_greedy.rs
/* Coin change: Greedy algorithm */\nfn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 {\n    // Assume coins list is sorted\n    let mut i = coins.len() - 1;\n    let mut count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while amt > 0 {\n        // Find the coin that is less than and closest to the remaining amount\n        while i > 0 && coins[i] > amt {\n            i -= 1;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count += 1;\n    }\n    // If no feasible solution is found, return -1\n    if amt == 0 {\n        count\n    } else {\n        -1\n    }\n}\n
coin_change_greedy.c
/* Coin change: Greedy algorithm */\nint coinChangeGreedy(int *coins, int size, int amt) {\n    // Assume coins list is sorted\n    int i = size - 1;\n    int count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt == 0 ? count : -1;\n}\n
coin_change_greedy.kt
/* Coin change: Greedy algorithm */\nfun coinChangeGreedy(coins: IntArray, amt: Int): Int {\n    // Assume coins list is sorted\n    var am = amt\n    var i = coins.size - 1\n    var count = 0\n    // Loop to make greedy choices until no remaining amount\n    while (am > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > am) {\n            i--\n        }\n        // Choose coins[i]\n        am -= coins[i]\n        count++\n    }\n    // If no feasible solution is found, return -1\n    return if (am == 0) count else -1\n}\n
coin_change_greedy.rb
### Coin change: greedy ###\ndef coin_change_greedy(coins, amt)\n  # Assume coins list is sorted\n  i = coins.length - 1\n  count = 0\n  # Loop to make greedy choices until no remaining amount\n  while amt > 0\n    # Find the coin that is less than and closest to the remaining amount\n    while i > 0 && coins[i] > amt\n      i -= 1\n    end\n    # Choose coins[i]\n    amt -= coins[i]\n    count += 1\n  end\n  # Return -1 if no solution found\n  amt == 0 ? count : -1\nend\n

You might exclaim: So clean! The greedy algorithm solves the coin change problem in about ten lines of code.

","path":["Chapter 15. Greedy","15.1   Greedy Algorithm"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1511-advantages-and-limitations-of-greedy-algorithms","level":2,"title":"15.1.1   Advantages and Limitations of Greedy Algorithms","text":"

Greedy algorithms are not only straightforward and simple to implement, but are also usually very efficient. In the code above, if the smallest coin denomination is \\(\\min(coins)\\), the greedy choice loops at most \\(amt / \\min(coins)\\) times, giving a time complexity of \\(O(amt / \\min(coins))\\). This is an order of magnitude smaller than the time complexity of the dynamic programming solution \\(O(n \\times amt)\\).

However, for certain coin denomination combinations, greedy algorithms cannot find the optimal solution. Figure 15-2 provides two examples.

  • Positive example \\(coins = [1, 5, 10, 20, 50, 100]\\): With this coin combination, given any \\(amt\\), the greedy algorithm can find the optimal solution.
  • Negative example \\(coins = [1, 20, 50]\\): Suppose \\(amt = 60\\), the greedy algorithm can only find the combination \\(50 + 1 \\times 10\\), totaling \\(11\\) coins, but dynamic programming can find the optimal solution \\(20 + 20 + 20\\), requiring only \\(3\\) coins.
  • Negative example \\(coins = [1, 49, 50]\\): Suppose \\(amt = 98\\), the greedy algorithm can only find the combination \\(50 + 1 \\times 48\\), totaling \\(49\\) coins, but dynamic programming can find the optimal solution \\(49 + 49\\), requiring only \\(2\\) coins.

Figure 15-2   Examples where greedy algorithms cannot find the optimal solution

In other words, for the coin change problem, greedy algorithms cannot guarantee finding the global optimal solution, and may even find very poor solutions. It is better suited for solving with dynamic programming.

Generally, the applicability of greedy algorithms falls into the following two situations.

  1. Can guarantee finding the optimal solution: In this situation, greedy algorithms are often the best choice, because they tend to be more efficient than backtracking and dynamic programming.
  2. Can find an approximate optimal solution: Greedy algorithms are also applicable in this situation. For many complex problems, finding the global optimal solution is very difficult, and being able to find a suboptimal solution with high efficiency is also very good.
","path":["Chapter 15. Greedy","15.1   Greedy Algorithm"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1512-characteristics-of-greedy-algorithms","level":2,"title":"15.1.2   Characteristics of Greedy Algorithms","text":"

So the question arises: what kind of problems are suitable for solving with greedy algorithms? Or in other words, under what conditions can greedy algorithms guarantee finding the optimal solution?

Compared to dynamic programming, the conditions for using greedy algorithms are stricter, mainly focusing on two properties of the problem.

  • Greedy choice property: Only when locally optimal choices can always lead to a globally optimal solution can greedy algorithms guarantee obtaining the optimal solution.
  • Optimal substructure: The optimal solution to the original problem contains the optimal solutions to subproblems.

Optimal substructure has already been introduced in the \"Dynamic Programming\" chapter, so we won't elaborate on it here. It's worth noting that the optimal substructure of some problems is not obvious, but they can still be solved using greedy algorithms.

We mainly explore methods for determining the greedy choice property. Although its description seems relatively simple, in practice, for many problems, proving the greedy choice property is not easy.

For example, in the coin change problem, although we can easily provide counterexamples to disprove the greedy choice property, proving it is quite difficult. If asked: what conditions must a coin combination satisfy to be solvable using a greedy algorithm? We often can only rely on intuition or examples to give an ambiguous answer, and find it difficult to provide a rigorous mathematical proof.

Quote

There is a paper that presents an algorithm with \\(O(n^3)\\) time complexity for determining whether a coin combination can use a greedy algorithm to find the optimal solution for any amount.

Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234.

","path":["Chapter 15. Greedy","15.1   Greedy Algorithm"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1513-steps-for-solving-problems-with-greedy-algorithms","level":2,"title":"15.1.3   Steps for Solving Problems with Greedy Algorithms","text":"

The problem-solving process for greedy problems can generally be divided into the following three steps.

  1. Problem analysis: Sort out and understand the problem characteristics, including state definition, optimization objectives, and constraints, etc. This step is also involved in backtracking and dynamic programming.
  2. Determine the greedy strategy: Determine how to make greedy choices at each step. This strategy should be able to reduce the problem size at each step, ultimately solving the entire problem.
  3. Correctness proof: It is usually necessary to prove that the problem has both greedy choice property and optimal substructure. This step may require mathematical proofs, such as mathematical induction or proof by contradiction.

Determining the greedy strategy is the core step in solving the problem, but it may not be easy to implement, mainly for the following reasons.

  • Greedy strategies differ greatly between different problems. For many problems, the greedy strategy is relatively straightforward, and we can derive it through some general thinking and attempts. However, for some complex problems, the greedy strategy may be very elusive, which really tests one's problem-solving experience and algorithmic ability.
  • Some greedy strategies are highly misleading. When we confidently design a greedy strategy, write the solution code and submit it for testing, we may find that some test cases cannot pass. This is because the designed greedy strategy is only \"partially correct\", as exemplified by the coin change problem discussed above.

To ensure correctness, we should rigorously mathematically prove the greedy strategy, usually using proof by contradiction or mathematical induction.

However, correctness proofs may also not be easy. If we have no clue, we usually choose to debug the code based on test cases, step by step modifying and verifying the greedy strategy.

","path":["Chapter 15. Greedy","15.1   Greedy Algorithm"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1514-typical-problems-solved-by-greedy-algorithms","level":2,"title":"15.1.4   Typical Problems Solved by Greedy Algorithms","text":"

Greedy algorithms are often applied to optimization problems that satisfy greedy choice property and optimal substructure. Below are some typical greedy algorithm problems.

  • Coin change problem: With certain coin combinations, greedy algorithms can always obtain the optimal solution.
  • Interval scheduling problem: Suppose you have some tasks, each taking place during a period of time, and your goal is to complete as many tasks as possible. If you always choose the task that ends earliest, then the greedy algorithm can obtain the optimal solution.
  • Fractional knapsack problem: Given a set of items and a carrying capacity, your goal is to select a set of items such that the total weight does not exceed the carrying capacity and the total value is maximized. If you always choose the item with the highest value-to-weight ratio (value / weight), then the greedy algorithm can obtain the optimal solution in some cases.
  • Stock trading problem: Given a set of historical stock prices, you can make multiple trades, but if you already hold stocks, you cannot buy again before selling, and the goal is to obtain the maximum profit.
  • Huffman coding: Huffman coding is a greedy algorithm used for lossless data compression. By constructing a Huffman tree and always merging the two nodes with the lowest frequency, the resulting Huffman tree has the minimum weighted path length (encoding length).
  • Dijkstra's algorithm: It is a greedy algorithm for solving the shortest path problem from a given source vertex to all other vertices.
","path":["Chapter 15. Greedy","15.1   Greedy Algorithm"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/","level":1,"title":"15.3   Max Capacity Problem","text":"

Question

Input an array \\(ht\\), where each element represents the height of a vertical partition. Any two partitions in the array, along with the space between them, can form a container.

The capacity of the container equals the product of height and width (area), where the height is determined by the shorter partition, and the width is the difference in array indices between the two partitions.

Please select two partitions in the array such that the capacity of the formed container is maximized, and return the maximum capacity. An example is shown in Figure 15-7.

Figure 15-7   Example data for the max capacity problem

The container is formed by any two partitions, therefore the state of this problem is the indices of two partitions, denoted as \\([i, j]\\).

According to the problem description, capacity equals height multiplied by width, where height is determined by the shorter partition, and width is the difference in array indices between the two partitions. Let the capacity be \\(cap[i, j]\\), then the calculation formula is:

\\[ cap[i, j] = \\min(ht[i], ht[j]) \\times (j - i) \\]

Let the array length be \\(n\\), then the number of combinations of two partitions (total number of states) is \\(C_n^2 = \\frac{n(n - 1)}{2}\\). Most directly, we can exhaustively enumerate all states to find the maximum capacity, with time complexity \\(O(n^2)\\).

","path":["Chapter 15. Greedy","15.3   Max Capacity Problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#1-greedy-strategy-determination","level":3,"title":"1.   Greedy Strategy Determination","text":"

This problem has a more efficient solution. As shown in Figure 15-8, select a state \\([i, j]\\) where index \\(i < j\\) and height \\(ht[i] < ht[j]\\), meaning \\(i\\) is the short partition and \\(j\\) is the long partition.

Figure 15-8   Initial state

As shown in Figure 15-9, if we now move the long partition \\(j\\) closer to the short partition \\(i\\), the capacity will definitely decrease.

This is because after moving the long partition \\(j\\), the width \\(j-i\\) definitely decreases; and since height is determined by the short partition, the height can only remain unchanged (\\(i\\) is still the short partition) or decrease (the moved \\(j\\) becomes the short partition).

Figure 15-9   State after moving the long partition inward

Conversely, we can only possibly increase capacity by contracting the short partition \\(i\\) inward. Because although width will definitely decrease, height may increase (the moved short partition \\(i\\) may become taller). For example, in Figure 15-10, the area increases after moving the short partition.

Figure 15-10   State after moving the short partition inward

From this we can derive the greedy strategy for this problem: initialize two pointers at both ends of the container, and in each round contract the pointer corresponding to the short partition inward, until the two pointers meet.

Figure 15-11 shows the execution process of the greedy strategy.

  1. In the initial state, pointers \\(i\\) and \\(j\\) are at both ends of the array.
  2. Calculate the capacity of the current state \\(cap[i, j]\\), and update the maximum capacity.
  3. Compare the heights of partition \\(i\\) and partition \\(j\\), and move the short partition inward by one position.
  4. Loop through steps 2. and 3. until \\(i\\) and \\(j\\) meet.
<1><2><3><4><5><6><7><8><9>

Figure 15-11   Greedy process for the max capacity problem

","path":["Chapter 15. Greedy","15.3   Max Capacity Problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"

The code loops at most \\(n\\) rounds, therefore the time complexity is \\(O(n)\\).

Variables \\(i\\), \\(j\\), and \\(res\\) use a constant amount of extra space, therefore the space complexity is \\(O(1)\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby max_capacity.py
def max_capacity(ht: list[int]) -> int:\n    \"\"\"Max capacity: Greedy algorithm\"\"\"\n    # Initialize i, j to be at both ends of the array\n    i, j = 0, len(ht) - 1\n    # Initial max capacity is 0\n    res = 0\n    # Loop for greedy selection until the two boards meet\n    while i < j:\n        # Update max capacity\n        cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        # Move the shorter board inward\n        if ht[i] < ht[j]:\n            i += 1\n        else:\n            j -= 1\n    return res\n
max_capacity.cpp
/* Max capacity: Greedy algorithm */\nint maxCapacity(vector<int> &ht) {\n    // Initialize i, j to be at both ends of the array\n    int i = 0, j = ht.size() - 1;\n    // Initial max capacity is 0\n    int res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        int cap = min(ht[i], ht[j]) * (j - i);\n        res = max(res, cap);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
max_capacity.java
/* Max capacity: Greedy algorithm */\nint maxCapacity(int[] ht) {\n    // Initialize i, j to be at both ends of the array\n    int i = 0, j = ht.length - 1;\n    // Initial max capacity is 0\n    int res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        int cap = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
max_capacity.cs
/* Max capacity: Greedy algorithm */\nint MaxCapacity(int[] ht) {\n    // Initialize i, j to be at both ends of the array\n    int i = 0, j = ht.Length - 1;\n    // Initial max capacity is 0\n    int res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        int cap = Math.Min(ht[i], ht[j]) * (j - i);\n        res = Math.Max(res, cap);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
max_capacity.go
/* Max capacity: Greedy algorithm */\nfunc maxCapacity(ht []int) int {\n    // Initialize i, j to be at both ends of the array\n    i, j := 0, len(ht)-1\n    // Initial max capacity is 0\n    res := 0\n    // Loop for greedy selection until the two boards meet\n    for i < j {\n        // Update max capacity\n        capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i)\n        res = int(math.Max(float64(res), float64(capacity)))\n        // Move the shorter board inward\n        if ht[i] < ht[j] {\n            i++\n        } else {\n            j--\n        }\n    }\n    return res\n}\n
max_capacity.swift
/* Max capacity: Greedy algorithm */\nfunc maxCapacity(ht: [Int]) -> Int {\n    // Initialize i, j to be at both ends of the array\n    var i = ht.startIndex, j = ht.endIndex - 1\n    // Initial max capacity is 0\n    var res = 0\n    // Loop for greedy selection until the two boards meet\n    while i < j {\n        // Update max capacity\n        let cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        // Move the shorter board inward\n        if ht[i] < ht[j] {\n            i += 1\n        } else {\n            j -= 1\n        }\n    }\n    return res\n}\n
max_capacity.js
/* Max capacity: Greedy algorithm */\nfunction maxCapacity(ht) {\n    // Initialize i, j to be at both ends of the array\n    let i = 0,\n        j = ht.length - 1;\n    // Initial max capacity is 0\n    let res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        const cap = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    return res;\n}\n
max_capacity.ts
/* Max capacity: Greedy algorithm */\nfunction maxCapacity(ht: number[]): number {\n    // Initialize i, j to be at both ends of the array\n    let i = 0,\n        j = ht.length - 1;\n    // Initial max capacity is 0\n    let res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        const cap: number = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    return res;\n}\n
max_capacity.dart
/* Max capacity: Greedy algorithm */\nint maxCapacity(List<int> ht) {\n  // Initialize i, j to be at both ends of the array\n  int i = 0, j = ht.length - 1;\n  // Initial max capacity is 0\n  int res = 0;\n  // Loop for greedy selection until the two boards meet\n  while (i < j) {\n    // Update max capacity\n    int cap = min(ht[i], ht[j]) * (j - i);\n    res = max(res, cap);\n    // Move the shorter board inward\n    if (ht[i] < ht[j]) {\n      i++;\n    } else {\n      j--;\n    }\n  }\n  return res;\n}\n
max_capacity.rs
/* Max capacity: Greedy algorithm */\nfn max_capacity(ht: &[i32]) -> i32 {\n    // Initialize i, j to be at both ends of the array\n    let mut i = 0;\n    let mut j = ht.len() - 1;\n    // Initial max capacity is 0\n    let mut res = 0;\n    // Loop for greedy selection until the two boards meet\n    while i < j {\n        // Update max capacity\n        let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32;\n        res = std::cmp::max(res, cap);\n        // Move the shorter board inward\n        if ht[i] < ht[j] {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    res\n}\n
max_capacity.c
/* Max capacity: Greedy algorithm */\nint maxCapacity(int ht[], int htLength) {\n    // Initialize i, j to be at both ends of the array\n    int i = 0;\n    int j = htLength - 1;\n    // Initial max capacity is 0\n    int res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        int capacity = myMin(ht[i], ht[j]) * (j - i);\n        res = myMax(res, capacity);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
max_capacity.kt
/* Max capacity: Greedy algorithm */\nfun maxCapacity(ht: IntArray): Int {\n    // Initialize i, j to be at both ends of the array\n    var i = 0\n    var j = ht.size - 1\n    // Initial max capacity is 0\n    var res = 0\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        val cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i++\n        } else {\n            j--\n        }\n    }\n    return res\n}\n
max_capacity.rb
### Maximum capacity: greedy ###\ndef max_capacity(ht)\n  # Initialize i, j to be at both ends of the array\n  i, j = 0, ht.length - 1\n  # Initial max capacity is 0\n  res = 0\n\n  # Loop for greedy selection until the two boards meet\n  while i < j\n    # Update max capacity\n    cap = [ht[i], ht[j]].min * (j - i)\n    res = [res, cap].max\n    # Move the shorter board inward\n    if ht[i] < ht[j]\n      i += 1\n    else\n      j -= 1\n    end\n  end\n\n  res\nend\n
","path":["Chapter 15. Greedy","15.3   Max Capacity Problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#3-correctness-proof","level":3,"title":"3.   Correctness Proof","text":"

The reason greedy is faster than exhaustive enumeration is that each round of greedy selection \"skips\" some states.

For example, in state \\(cap[i, j]\\) where \\(i\\) is the short partition and \\(j\\) is the long partition, if we greedily move the short partition \\(i\\) inward by one position, the states shown in Figure 15-12 will be \"skipped\". This means that the capacities of these states cannot be verified later.

\\[ cap[i, i+1], cap[i, i+2], \\dots, cap[i, j-2], cap[i, j-1] \\]

Figure 15-12   States skipped by moving the short partition

Observing carefully, these skipped states are actually all the states obtained by moving the long partition \\(j\\) inward. We have already proven that moving the long partition inward will definitely decrease capacity. That is, the skipped states cannot possibly be the optimal solution, skipping them will not cause us to miss the optimal solution.

The above analysis shows that the operation of moving the short partition is \"safe\", and the greedy strategy is effective.

","path":["Chapter 15. Greedy","15.3   Max Capacity Problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/","level":1,"title":"15.4   Max Product Cutting Problem","text":"

Question

Given a positive integer \\(n\\), split it into the sum of at least two positive integers, and find the maximum product of all integers after splitting, as shown in Figure 15-13.

Figure 15-13   Problem definition of max product cutting

Suppose we split \\(n\\) into \\(m\\) integer factors, where the \\(i\\)-th factor is denoted as \\(n_i\\), that is

\\[ n = \\sum_{i=1}^{m}n_i \\]

The goal of this problem is to find the maximum product of all integer factors, namely

\\[ \\max(\\prod_{i=1}^{m}n_i) \\]

We need to think about: how large should the splitting count \\(m\\) be, and what should each \\(n_i\\) be?

","path":["Chapter 15. Greedy","15.4   Max Product Cutting Problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#1-greedy-strategy-determination","level":3,"title":"1.   Greedy Strategy Determination","text":"

Based on experience, the product of two integers is often greater than their sum. Suppose we split out a factor of \\(2\\) from \\(n\\), then their product is \\(2(n-2)\\). We compare this product with \\(n\\):

\\[ \\begin{aligned} 2(n-2) & \\geq n \\newline 2n - n - 4 & \\geq 0 \\newline n & \\geq 4 \\end{aligned} \\]

As shown in Figure 15-14, when \\(n \\geq 4\\), splitting out a \\(2\\) will increase the product, which indicates that integers greater than or equal to \\(4\\) should all be split.

Greedy strategy one: If the splitting scheme includes factors \\(\\geq 4\\), then they should continue to be split. The final splitting scheme should only contain factors \\(1\\), \\(2\\), and \\(3\\).

Figure 15-14   Splitting causes product to increase

Next, consider which factor is optimal. Among the three factors \\(1\\), \\(2\\), and \\(3\\), clearly \\(1\\) is the worst, because \\(1 \\times (n-1) < n\\) always holds, meaning splitting out \\(1\\) will actually decrease the product.

As shown in Figure 15-15, when \\(n = 6\\), we have \\(3 \\times 3 > 2 \\times 2 \\times 2\\). This means that splitting out \\(3\\) is better than splitting out \\(2\\).

Greedy strategy two: In the splitting scheme, there should be at most two \\(2\\)s. Because three \\(2\\)s can always be replaced by two \\(3\\)s to obtain a larger product.

Figure 15-15   Optimal splitting factor

In summary, the following greedy strategies can be derived.

  1. Input integer \\(n\\), continuously split out factor \\(3\\) until the remainder is \\(0\\), \\(1\\), or \\(2\\).
  2. When the remainder is \\(0\\), it means \\(n\\) is a multiple of \\(3\\), so no further action is needed.
  3. When the remainder is \\(2\\), do not continue splitting, keep it.
  4. When the remainder is \\(1\\), since \\(2 \\times 2 > 1 \\times 3\\), the last \\(3\\) should be replaced with \\(2\\).
","path":["Chapter 15. Greedy","15.4   Max Product Cutting Problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"

As shown in Figure 15-16, we don't need to use loops to split the integer, but can use integer division to get the count of \\(3\\)s as \\(a\\), and modulo operation to get the remainder as \\(b\\), at which point we have:

\\[ n = 3 a + b \\]

Please note that for the edge case of \\(n \\leq 3\\), a \\(1\\) must be split out, with product \\(1 \\times (n - 1)\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby max_product_cutting.py
def max_product_cutting(n: int) -> int:\n    \"\"\"Max product cutting: Greedy algorithm\"\"\"\n    # When n <= 3, must cut out a 1\n    if n <= 3:\n        return 1 * (n - 1)\n    # Greedily cut out 3, a is the number of 3s, b is the remainder\n    a, b = n // 3, n % 3\n    if b == 1:\n        # When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return int(math.pow(3, a - 1)) * 2 * 2\n    if b == 2:\n        # When the remainder is 2, do nothing\n        return int(math.pow(3, a)) * 2\n    # When the remainder is 0, do nothing\n    return int(math.pow(3, a))\n
max_product_cutting.cpp
/* Max product cutting: Greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return (int)pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, do nothing\n        return (int)pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return (int)pow(3, a);\n}\n
max_product_cutting.java
/* Max product cutting: Greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return (int) Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, do nothing\n        return (int) Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return (int) Math.pow(3, a);\n}\n
max_product_cutting.cs
/* Max product cutting: Greedy algorithm */\nint MaxProductCutting(int n) {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return (int)Math.Pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, do nothing\n        return (int)Math.Pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return (int)Math.Pow(3, a);\n}\n
max_product_cutting.go
/* Max product cutting: Greedy algorithm */\nfunc maxProductCutting(n int) int {\n    // When n <= 3, must cut out a 1\n    if n <= 3 {\n        return 1 * (n - 1)\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    a := n / 3\n    b := n % 3\n    if b == 1 {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return int(math.Pow(3, float64(a-1))) * 2 * 2\n    }\n    if b == 2 {\n        // When the remainder is 2, do nothing\n        return int(math.Pow(3, float64(a))) * 2\n    }\n    // When the remainder is 0, do nothing\n    return int(math.Pow(3, float64(a)))\n}\n
max_product_cutting.swift
/* Max product cutting: Greedy algorithm */\nfunc maxProductCutting(n: Int) -> Int {\n    // When n <= 3, must cut out a 1\n    if n <= 3 {\n        return 1 * (n - 1)\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    let a = n / 3\n    let b = n % 3\n    if b == 1 {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return pow(3, a - 1) * 2 * 2\n    }\n    if b == 2 {\n        // When the remainder is 2, do nothing\n        return pow(3, a) * 2\n    }\n    // When the remainder is 0, do nothing\n    return pow(3, a)\n}\n
max_product_cutting.js
/* Max product cutting: Greedy algorithm */\nfunction maxProductCutting(n) {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    let a = Math.floor(n / 3);\n    let b = n % 3;\n    if (b === 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b === 2) {\n        // When the remainder is 2, do nothing\n        return Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return Math.pow(3, a);\n}\n
max_product_cutting.ts
/* Max product cutting: Greedy algorithm */\nfunction maxProductCutting(n: number): number {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    let a: number = Math.floor(n / 3);\n    let b: number = n % 3;\n    if (b === 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b === 2) {\n        // When the remainder is 2, do nothing\n        return Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return Math.pow(3, a);\n}\n
max_product_cutting.dart
/* Max product cutting: Greedy algorithm */\nint maxProductCutting(int n) {\n  // When n <= 3, must cut out a 1\n  if (n <= 3) {\n    return 1 * (n - 1);\n  }\n  // Greedily cut out 3, a is the number of 3s, b is the remainder\n  int a = n ~/ 3;\n  int b = n % 3;\n  if (b == 1) {\n    // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n    return (pow(3, a - 1) * 2 * 2).toInt();\n  }\n  if (b == 2) {\n    // When the remainder is 2, do nothing\n    return (pow(3, a) * 2).toInt();\n  }\n  // When the remainder is 0, do nothing\n  return pow(3, a).toInt();\n}\n
max_product_cutting.rs
/* Max product cutting: Greedy algorithm */\nfn max_product_cutting(n: i32) -> i32 {\n    // When n <= 3, must cut out a 1\n    if n <= 3 {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    let a = n / 3;\n    let b = n % 3;\n    if b == 1 {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        3_i32.pow(a as u32 - 1) * 2 * 2\n    } else if b == 2 {\n        // When the remainder is 2, do nothing\n        3_i32.pow(a as u32) * 2\n    } else {\n        // When the remainder is 0, do nothing\n        3_i32.pow(a as u32)\n    }\n}\n
max_product_cutting.c
/* Max product cutting: Greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, do nothing\n        return pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return pow(3, a);\n}\n
max_product_cutting.kt
/* Max product cutting: Greedy algorithm */\nfun maxProductCutting(n: Int): Int {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1)\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    val a = n / 3\n    val b = n % 3\n    if (b == 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return 3.0.pow((a - 1)).toInt() * 2 * 2\n    }\n    if (b == 2) {\n        // When the remainder is 2, do nothing\n        return 3.0.pow(a).toInt() * 2 * 2\n    }\n    // When the remainder is 0, do nothing\n    return 3.0.pow(a).toInt()\n}\n
max_product_cutting.rb
### Maximum cutting product: greedy ###\ndef max_product_cutting(n)\n  # When n <= 3, must cut out a 1\n  return 1 * (n - 1) if n <= 3\n  # Greedily cut out 3, a is the number of 3s, b is the remainder\n  a, b = n / 3, n % 3\n  # When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n  return (3.pow(a - 1) * 2 * 2).to_i if b == 1\n  # When the remainder is 2, do nothing\n  return (3.pow(a) * 2).to_i if b == 2\n  # When the remainder is 0, do nothing\n  3.pow(a).to_i\nend\n

Figure 15-16   Calculation method for max product cutting

The time complexity depends on the implementation of the exponentiation operation in the programming language. Taking Python as an example, there are three commonly used power calculation functions.

  • Both the operator ** and the function pow() have time complexity \\(O(\\log⁡ a)\\).
  • The function math.pow() internally calls the C library's pow() function, which performs floating-point exponentiation, with time complexity \\(O(1)\\).

Variables \\(a\\) and \\(b\\) use a constant amount of extra space, therefore the space complexity is \\(O(1)\\).

","path":["Chapter 15. Greedy","15.4   Max Product Cutting Problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#3-correctness-proof","level":3,"title":"3.   Correctness Proof","text":"

Using proof by contradiction, only analyzing the case where \\(n \\geq 4\\).

  1. All factors \\(\\leq 3\\): Suppose the optimal splitting scheme includes a factor \\(x \\geq 4\\), then it can definitely continue to be split into \\(2(x-2)\\) to obtain a larger (or equal) product. This contradicts the assumption.
  2. The splitting scheme does not contain \\(1\\): Suppose the optimal splitting scheme includes a factor of \\(1\\), then it can definitely be merged into another factor to obtain a larger product. This contradicts the assumption.
  3. The splitting scheme contains at most two \\(2\\)s: Suppose the optimal splitting scheme includes three \\(2\\)s, then they can definitely be replaced by two \\(3\\)s for a larger product. This contradicts the assumption.
","path":["Chapter 15. Greedy","15.4   Max Product Cutting Problem"],"tags":[]},{"location":"chapter_greedy/summary/","level":1,"title":"15.5   Summary","text":"","path":["Chapter 15. Greedy","15.5   Summary"],"tags":[]},{"location":"chapter_greedy/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Greedy algorithms are typically used to solve optimization problems. The principle is to make locally optimal decisions at each decision stage in hopes of obtaining a globally optimal solution.
  • Greedy algorithms iteratively make one greedy choice after another, transforming the problem into a smaller subproblem in each round, until the problem is solved.
  • Greedy algorithms are not only simple to implement, but also have high problem-solving efficiency. Compared to dynamic programming, greedy algorithms typically have lower time complexity.
  • In the coin change problem, for certain coin combinations, greedy algorithms can guarantee finding the optimal solution; for other coin combinations, however, greedy algorithms may find very poor solutions.
  • Problems suitable for solving with greedy algorithms have two major properties: greedy choice property and optimal substructure. The greedy choice property represents the effectiveness of the greedy strategy.
  • For some complex problems, proving the greedy choice property is not simple. Relatively speaking, disproving it is easier, such as in the coin change problem.
  • Solving greedy problems mainly consists of three steps: problem analysis, determining the greedy strategy, and correctness proof. Among these, determining the greedy strategy is the core step, and correctness proof is often the difficult point.
  • The fractional knapsack problem, based on the 0-1 knapsack problem, allows selecting a portion of items, and therefore can be solved using greedy algorithms. The correctness of the greedy strategy can be proven using proof by contradiction.
  • The max capacity problem can be solved using exhaustive enumeration with time complexity \\(O(n^2)\\). By designing a greedy strategy to move the short partition inward in each round, the time complexity can be optimized to \\(O(n)\\).
  • In the max product cutting problem, we successively derive two greedy strategies: integers \\(\\geq 4\\) should all continue to be split, and the optimal splitting factor is \\(3\\). The code includes exponentiation operations, and the time complexity depends on the implementation method of exponentiation, typically being \\(O(1)\\) or \\(O(\\log n)\\).
","path":["Chapter 15. Greedy","15.5   Summary"],"tags":[]},{"location":"chapter_hashing/","level":1,"title":"Chapter 6.   Hashing","text":"

Abstract

In the world of computing, a hash table is like a clever librarian.

They know how to calculate call numbers, enabling them to quickly locate the target book.

","path":["Chapter 6. Hashing","Chapter 6.   Hashing"],"tags":[]},{"location":"chapter_hashing/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 6.1   Hash Table
  • 6.2   Hash Collision
  • 6.3   Hash Algorithm
  • 6.4   Summary
","path":["Chapter 6. Hashing","Chapter 6.   Hashing"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/","level":1,"title":"6.3   Hash Algorithm","text":"

The previous two sections introduced the working principle of hash tables and the methods to handle hash collisions. However, both open addressing and separate chaining can only ensure that the hash table functions normally when hash collisions occur, but cannot reduce the frequency of hash collisions.

If hash collisions occur too frequently, the performance of the hash table will deteriorate drastically. As shown in Figure 6-8, for a separate chaining hash table, in the ideal case, the key-value pairs are evenly distributed across the buckets, achieving optimal query efficiency; in the worst case, all key-value pairs are stored in the same bucket, degrading the time complexity to \\(O(n)\\).

Figure 6-8   Ideal and worst cases of hash collisions

The distribution of key-value pairs is determined by the hash function. Recalling the calculation steps of the hash function, first compute the hash value, then take the modulo by the array length:

index = hash(key) % capacity\n

Observing the above formula, when the hash table capacity capacity is fixed, the hash algorithm hash() determines the output value, thereby determining the distribution of key-value pairs in the hash table.

This means that, to reduce the probability of hash collisions, we should focus on the design of the hash algorithm hash().

","path":["Chapter 6. Hashing","6.3   Hash Algorithm"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#631-goals-of-hash-algorithms","level":2,"title":"6.3.1   Goals of Hash Algorithms","text":"

To achieve a \"fast and stable\" hash table data structure, hash algorithms should have the following characteristics:

  • Determinism: For the same input, the hash algorithm should always produce the same output. Only then can the hash table be reliable.
  • High efficiency: The process of computing the hash value should be fast enough. The smaller the computational overhead, the more practical the hash table.
  • Uniform distribution: The hash algorithm should ensure that key-value pairs are evenly distributed in the hash table. The more uniform the distribution, the lower the probability of hash collisions.

In fact, hash algorithms are not only used to implement hash tables but are also widely applied in other fields.

  • Password storage: To protect the security of user passwords, systems usually do not store the plaintext passwords but rather the hash values of the passwords. When a user enters a password, the system calculates the hash value of the input and compares it with the stored hash value. If they match, the password is considered correct.
  • Data integrity check: The data sender can calculate the hash value of the data and send it along; the receiver can recalculate the hash value of the received data and compare it with the received hash value. If they match, the data is considered intact.

For cryptographic applications, to prevent reverse engineering such as deducing the original password from the hash value, hash algorithms need higher-level security features.

  • Unidirectionality: It should be impossible to deduce any information about the input data from the hash value.
  • Collision resistance: It should be extremely difficult to find two different inputs that produce the same hash value.
  • Avalanche effect: Minor changes in the input should lead to significant and unpredictable changes in the output.

Note that \"uniform distribution\" and \"collision resistance\" are two independent concepts. Satisfying uniform distribution does not necessarily mean collision resistance. For example, under random input key, the hash function key % 100 can produce a uniformly distributed output. However, this hash algorithm is too simple, and all key with the same last two digits will have the same output, making it easy to deduce a usable key from the hash value, thereby cracking the password.

","path":["Chapter 6. Hashing","6.3   Hash Algorithm"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#632-design-of-hash-algorithms","level":2,"title":"6.3.2   Design of Hash Algorithms","text":"

The design of hash algorithms is a complex issue that requires consideration of many factors. However, for some less demanding scenarios, we can also design some simple hash algorithms.

  • Additive hash: Add up the ASCII codes of each character in the input and use the total sum as the hash value.
  • Multiplicative hash: Utilize the non-correlation of multiplication, multiplying each round by a constant, accumulating the ASCII codes of each character into the hash value.
  • XOR hash: Accumulate the hash value by XORing each element of the input data.
  • Rotating hash: Accumulate the ASCII code of each character into a hash value, performing a rotation operation on the hash value before each accumulation.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby simple_hash.py
def add_hash(key: str) -> int:\n    \"\"\"Additive hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash += ord(c)\n    return hash % modulus\n\ndef mul_hash(key: str) -> int:\n    \"\"\"Multiplicative hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash = 31 * hash + ord(c)\n    return hash % modulus\n\ndef xor_hash(key: str) -> int:\n    \"\"\"XOR hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash ^= ord(c)\n    return hash % modulus\n\ndef rot_hash(key: str) -> int:\n    \"\"\"Rotational hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash = (hash << 4) ^ (hash >> 28) ^ ord(c)\n    return hash % modulus\n
simple_hash.cpp
/* Additive hash */\nint addHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = (hash + (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint mulHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = (31 * hash + (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint xorHash(string key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash ^= (int)c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotational hash */\nint rotHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n
simple_hash.java
/* Additive hash */\nint addHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = (hash + (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n\n/* Multiplicative hash */\nint mulHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = (31 * hash + (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n\n/* XOR hash */\nint xorHash(String key) {\n    int hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash ^= (int) c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotational hash */\nint rotHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n
simple_hash.cs
/* Additive hash */\nint AddHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = (hash + c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint MulHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = (31 * hash + c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint XorHash(string key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash ^= c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotational hash */\nint RotHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS;\n    }\n    return (int)hash;\n}\n
simple_hash.go
/* Additive hash */\nfunc addHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = (hash + int64(b)) % modulus\n    }\n    return int(hash)\n}\n\n/* Multiplicative hash */\nfunc mulHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = (31*hash + int64(b)) % modulus\n    }\n    return int(hash)\n}\n\n/* XOR hash */\nfunc xorHash(key string) int {\n    hash := 0\n    modulus := 1000000007\n    for _, b := range []byte(key) {\n        fmt.Println(int(b))\n        hash ^= int(b)\n        hash = (31*hash + int(b)) % modulus\n    }\n    return hash & modulus\n}\n\n/* Rotational hash */\nfunc rotHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus\n    }\n    return int(hash)\n}\n
simple_hash.swift
/* Additive hash */\nfunc addHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = (hash + Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n\n/* Multiplicative hash */\nfunc mulHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = (31 * hash + Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n\n/* XOR hash */\nfunc xorHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash ^= Int(scalar.value)\n        }\n    }\n    return hash & MODULUS\n}\n\n/* Rotational hash */\nfunc rotHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n
simple_hash.js
/* Additive hash */\nfunction addHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* Multiplicative hash */\nfunction mulHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (31 * hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* XOR hash */\nfunction xorHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash ^= c.charCodeAt(0);\n    }\n    return hash % MODULUS;\n}\n\n/* Rotational hash */\nfunction rotHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n
simple_hash.ts
/* Additive hash */\nfunction addHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* Multiplicative hash */\nfunction mulHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (31 * hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* XOR hash */\nfunction xorHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash ^= c.charCodeAt(0);\n    }\n    return hash % MODULUS;\n}\n\n/* Rotational hash */\nfunction rotHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n
simple_hash.dart
/* Additive hash */\nint addHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = (hash + key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n\n/* Multiplicative hash */\nint mulHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = (31 * hash + key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n\n/* XOR hash */\nint xorHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash ^= key.codeUnitAt(i);\n  }\n  return hash & MODULUS;\n}\n\n/* Rotational hash */\nint rotHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n
simple_hash.rs
/* Additive hash */\nfn add_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = (hash + c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n\n/* Multiplicative hash */\nfn mul_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = (31 * hash + c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n\n/* XOR hash */\nfn xor_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash ^= c as i64;\n    }\n\n    (hash & MODULUS) as i32\n}\n\n/* Rotational hash */\nfn rot_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n
simple_hash.c
/* Additive hash */\nint addHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = (hash + (unsigned char)key[i]) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint mulHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = (31 * hash + (unsigned char)key[i]) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint xorHash(char *key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n\n    for (int i = 0; i < strlen(key); i++) {\n        hash ^= (unsigned char)key[i];\n    }\n    return hash & MODULUS;\n}\n\n/* Rotational hash */\nint rotHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS;\n    }\n\n    return (int)hash;\n}\n
simple_hash.kt
/* Additive hash */\nfun addHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = (hash + c.code) % MODULUS\n    }\n    return hash.toInt()\n}\n\n/* Multiplicative hash */\nfun mulHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = (31 * hash + c.code) % MODULUS\n    }\n    return hash.toInt()\n}\n\n/* XOR hash */\nfun xorHash(key: String): Int {\n    var hash = 0\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = hash xor c.code\n    }\n    return hash and MODULUS\n}\n\n/* Rotational hash */\nfun rotHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS\n    }\n    return hash.toInt()\n}\n
simple_hash.rb
### Additive hash ###\ndef add_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash += c.ord }\n\n  hash % modulus\nend\n\n### Multiplicative hash ###\ndef mul_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash = 31 * hash + c.ord }\n\n  hash % modulus\nend\n\n### XOR hash ###\ndef xor_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash ^= c.ord }\n\n  hash % modulus\nend\n\n### Rotational hash ###\ndef rot_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord }\n\n  hash % modulus\nend\n

It is observed that the last step of each hash algorithm is to take the modulus of the large prime number \\(1000000007\\) to ensure that the hash value is within an appropriate range. It is worth pondering why emphasis is placed on modulo a prime number, or what are the disadvantages of modulo a composite number? This is an interesting question.

To conclude: Using a large prime number as the modulus can maximize the uniform distribution of hash values. Since a prime number does not share common factors with other numbers, it can reduce the periodic patterns caused by the modulo operation, thus avoiding hash collisions.

For example, suppose we choose the composite number \\(9\\) as the modulus, which can be divided by \\(3\\), then all key divisible by \\(3\\) will be mapped to hash values \\(0\\), \\(3\\), \\(6\\).

\\[ \\begin{aligned} \\text{modulus} & = 9 \\newline \\text{key} & = \\{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \\dots \\} \\newline \\text{hash} & = \\{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\\dots \\} \\end{aligned} \\]

If the input key happens to have this kind of arithmetic sequence distribution, then the hash values will cluster, thereby exacerbating hash collisions. Now, suppose we replace modulus with the prime number \\(13\\), since there are no common factors between key and modulus, the uniformity of the output hash values will be significantly improved.

\\[ \\begin{aligned} \\text{modulus} & = 13 \\newline \\text{key} & = \\{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \\dots \\} \\newline \\text{hash} & = \\{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \\dots \\} \\end{aligned} \\]

It is worth noting that if the key is guaranteed to be randomly and uniformly distributed, then choosing a prime number or a composite number as the modulus can both produce uniformly distributed hash values. However, when the distribution of key has some periodicity, modulo a composite number is more likely to result in clustering.

In summary, we usually choose a prime number as the modulus, and this prime number should be large enough to eliminate periodic patterns as much as possible, enhancing the robustness of the hash algorithm.

","path":["Chapter 6. Hashing","6.3   Hash Algorithm"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#633-common-hash-algorithms","level":2,"title":"6.3.3   Common Hash Algorithms","text":"

It is not hard to see that the simple hash algorithms mentioned above are quite \"fragile\" and far from reaching the design goals of hash algorithms. For example, since addition and XOR obey the commutative law, additive hash and XOR hash cannot distinguish strings with the same content but in different order, which may exacerbate hash collisions and cause security issues.

In practice, we usually use some standard hash algorithms, such as MD5, SHA-1, SHA-2, and SHA-3. They can map input data of any length to a fixed-length hash value.

Over the past century, hash algorithms have been in a continuous process of upgrading and optimization. Some researchers strive to improve the performance of hash algorithms, while others, including hackers, are dedicated to finding security issues in hash algorithms. Table 6-2 shows hash algorithms commonly used in practical applications.

  • MD5 and SHA-1 have been successfully attacked multiple times and are thus abandoned in various security applications.
  • SHA-2 series, especially SHA-256, is one of the most secure hash algorithms to date, with no successful attacks reported, hence commonly used in various security applications and protocols.
  • SHA-3 has lower implementation costs and higher computational efficiency compared to SHA-2, but its current usage coverage is not as extensive as the SHA-2 series.

Table 6-2   Common hash algorithms

MD5 SHA-1 SHA-2 SHA-3 Release Year 1992 1995 2002 2008 Output Length 128 bit 160 bit 256/512 bit 224/256/384/512 bit Hash Collisions Frequent Frequent Rare Rare Security Level Low, has been successfully attacked Low, has been successfully attacked High High Applications Abandoned, still used for data integrity checks Abandoned Cryptocurrency transaction verification, digital signatures, etc. Can be used to replace SHA-2","path":["Chapter 6. Hashing","6.3   Hash Algorithm"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#hash-values-in-data-structures","level":1,"title":"Hash Values in Data Structures","text":"

We know that the keys in a hash table can be of various data types such as integers, decimals, or strings. Programming languages usually provide built-in hash algorithms for these data types to calculate the bucket indices in the hash table. Taking Python as an example, we can use the hash() function to compute the hash values for various data types.

  • The hash values of integers and booleans are their own values.
  • The calculation of hash values for floating-point numbers and strings is more complex, and interested readers are encouraged to study this on their own.
  • The hash value of a tuple is a combination of the hash values of each of its elements, resulting in a single hash value.
  • The hash value of an object is generated based on its memory address. By overriding the hash method of an object, hash values can be generated based on content.

Tip

Be aware that the definition and methods of the built-in hash value calculation functions in different programming languages vary.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby built_in_hash.py
num = 3\nhash_num = hash(num)\n# Hash value of integer 3 is 3\n\nbol = True\nhash_bol = hash(bol)\n# Hash value of boolean True is 1\n\ndec = 3.14159\nhash_dec = hash(dec)\n# Hash value of decimal 3.14159 is 326484311674566659\n\nstr = \"Hello 算法\"\nhash_str = hash(str)\n# Hash value of string \"Hello 算法\" is 4617003410720528961\n\ntup = (12836, \"小哈\")\nhash_tup = hash(tup)\n# Hash value of tuple (12836, '小哈') is 1029005403108185979\n\nobj = ListNode(0)\nhash_obj = hash(obj)\n# Hash value of ListNode object at 0x1058fd810 is 274267521\n
built_in_hash.cpp
int num = 3;\nsize_t hashNum = hash<int>()(num);\n// Hash value of integer 3 is 3\n\nbool bol = true;\nsize_t hashBol = hash<bool>()(bol);\n// Hash value of boolean 1 is 1\n\ndouble dec = 3.14159;\nsize_t hashDec = hash<double>()(dec);\n// Hash value of decimal 3.14159 is 4614256650576692846\n\nstring str = \"Hello 算法\";\nsize_t hashStr = hash<string>()(str);\n// Hash value of string \"Hello 算法\" is 15466937326284535026\n\n// In C++, built-in std::hash() only provides hash values for basic data types\n// Hash values for arrays and objects need to be implemented separately\n
built_in_hash.java
int num = 3;\nint hashNum = Integer.hashCode(num);\n// Hash value of integer 3 is 3\n\nboolean bol = true;\nint hashBol = Boolean.hashCode(bol);\n// Hash value of boolean true is 1231\n\ndouble dec = 3.14159;\nint hashDec = Double.hashCode(dec);\n// Hash value of decimal 3.14159 is -1340954729\n\nString str = \"Hello 算法\";\nint hashStr = str.hashCode();\n// Hash value of string \"Hello 算法\" is -727081396\n\nObject[] arr = { 12836, \"小哈\" };\nint hashTup = Arrays.hashCode(arr);\n// Hash value of array [12836, 小哈] is 1151158\n\nListNode obj = new ListNode(0);\nint hashObj = obj.hashCode();\n// Hash value of ListNode object utils.ListNode@7dc5e7b4 is 2110121908\n
built_in_hash.cs
int num = 3;\nint hashNum = num.GetHashCode();\n// Hash value of integer 3 is 3;\n\nbool bol = true;\nint hashBol = bol.GetHashCode();\n// Hash value of boolean true is 1;\n\ndouble dec = 3.14159;\nint hashDec = dec.GetHashCode();\n// Hash value of decimal 3.14159 is -1340954729;\n\nstring str = \"Hello 算法\";\nint hashStr = str.GetHashCode();\n// Hash value of string \"Hello 算法\" is -586107568;\n\nobject[] arr = [12836, \"小哈\"];\nint hashTup = arr.GetHashCode();\n// Hash value of array [12836, 小哈] is 42931033;\n\nListNode obj = new(0);\nint hashObj = obj.GetHashCode();\n// Hash value of ListNode object 0 is 39053774;\n
built_in_hash.go
// Go does not provide built-in hash code functions\n
built_in_hash.swift
let num = 3\nlet hashNum = num.hashValue\n// Hash value of integer 3 is 9047044699613009734\n\nlet bol = true\nlet hashBol = bol.hashValue\n// Hash value of boolean true is -4431640247352757451\n\nlet dec = 3.14159\nlet hashDec = dec.hashValue\n// Hash value of decimal 3.14159 is -2465384235396674631\n\nlet str = \"Hello 算法\"\nlet hashStr = str.hashValue\n// Hash value of string \"Hello 算法\" is -7850626797806988787\n\nlet arr = [AnyHashable(12836), AnyHashable(\"小哈\")]\nlet hashTup = arr.hashValue\n// Hash value of array [AnyHashable(12836), AnyHashable(\"小哈\")] is -2308633508154532996\n\nlet obj = ListNode(x: 0)\nlet hashObj = obj.hashValue\n// Hash value of ListNode object utils.ListNode is -2434780518035996159\n
built_in_hash.js
// JavaScript does not provide built-in hash code functions\n
built_in_hash.ts
// TypeScript does not provide built-in hash code functions\n
built_in_hash.dart
int num = 3;\nint hashNum = num.hashCode;\n// Hash value of integer 3 is 34803\n\nbool bol = true;\nint hashBol = bol.hashCode;\n// Hash value of boolean true is 1231\n\ndouble dec = 3.14159;\nint hashDec = dec.hashCode;\n// Hash value of decimal 3.14159 is 2570631074981783\n\nString str = \"Hello 算法\";\nint hashStr = str.hashCode;\n// Hash value of string \"Hello 算法\" is 468167534\n\nList arr = [12836, \"小哈\"];\nint hashArr = arr.hashCode;\n// Hash value of array [12836, 小哈] is 976512528\n\nListNode obj = new ListNode(0);\nint hashObj = obj.hashCode;\n// Hash value of ListNode object Instance of 'ListNode' is 1033450432\n
built_in_hash.rs
use std::collections::hash_map::DefaultHasher;\nuse std::hash::{Hash, Hasher};\n\nlet num = 3;\nlet mut num_hasher = DefaultHasher::new();\nnum.hash(&mut num_hasher);\nlet hash_num = num_hasher.finish();\n// Hash value of integer 3 is 568126464209439262\n\nlet bol = true;\nlet mut bol_hasher = DefaultHasher::new();\nbol.hash(&mut bol_hasher);\nlet hash_bol = bol_hasher.finish();\n// Hash value of boolean true is 4952851536318644461\n\nlet dec: f32 = 3.14159;\nlet mut dec_hasher = DefaultHasher::new();\ndec.to_bits().hash(&mut dec_hasher);\nlet hash_dec = dec_hasher.finish();\n// Hash value of decimal 3.14159 is 2566941990314602357\n\nlet str = \"Hello 算法\";\nlet mut str_hasher = DefaultHasher::new();\nstr.hash(&mut str_hasher);\nlet hash_str = str_hasher.finish();\n// Hash value of string \"Hello 算法\" is 16092673739211250988\n\nlet arr = (&12836, &\"小哈\");\nlet mut tup_hasher = DefaultHasher::new();\narr.hash(&mut tup_hasher);\nlet hash_tup = tup_hasher.finish();\n// Hash value of tuple (12836, \"小哈\") is 1885128010422702749\n\nlet node = ListNode::new(42);\nlet mut hasher = DefaultHasher::new();\nnode.borrow().val.hash(&mut hasher);\nlet hash = hasher.finish();\n// Hash value of ListNode object RefCell { value: ListNode { val: 42, next: None } } is 15387811073369036852\n
built_in_hash.c
// C does not provide built-in hash code functions\n
built_in_hash.kt
val num = 3\nval hashNum = num.hashCode()\n// Hash value of integer 3 is 3\n\nval bol = true\nval hashBol = bol.hashCode()\n// Hash value of boolean true is 1231\n\nval dec = 3.14159\nval hashDec = dec.hashCode()\n// Hash value of decimal 3.14159 is -1340954729\n\nval str = \"Hello 算法\"\nval hashStr = str.hashCode()\n// Hash value of string \"Hello 算法\" is -727081396\n\nval arr = arrayOf<Any>(12836, \"小哈\")\nval hashTup = arr.hashCode()\n// Hash value of array [12836, 小哈] is 189568618\n\nval obj = ListNode(0)\nval hashObj = obj.hashCode()\n// Hash value of ListNode object utils.ListNode@1d81eb93 is 495053715\n
built_in_hash.rb
num = 3\nhash_num = num.hash\n# Hash value of integer 3 is -4385856518450339636\n\nbol = true\nhash_bol = bol.hash\n# Hash value of boolean true is -1617938112149317027\n\ndec = 3.14159\nhash_dec = dec.hash\n# Hash value of decimal 3.14159 is -1479186995943067893\n\nstr = \"Hello 算法\"\nhash_str = str.hash\n# Hash value of string \"Hello 算法\" is -4075943250025831763\n\ntup = [12836, '小哈']\nhash_tup = tup.hash\n# Hash value of tuple (12836, '小哈') is 1999544809202288822\n\nobj = ListNode.new(0)\nhash_obj = obj.hash\n# Hash value of ListNode object #<ListNode:0x000078133140ab70> is 4302940560806366381\n
Visualized Execution

https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

In many programming languages, only immutable objects can serve as the key in a hash table. If we use a list (dynamic array) as a key, when the contents of the list change, its hash value also changes, and we would no longer be able to find the original value in the hash table.

Although the member variables of a custom object (such as a linked list node) are mutable, it is hashable. This is because the hash value of an object is usually generated based on its memory address, and even if the contents of the object change, the memory address remains the same, so the hash value remains unchanged.

You might have noticed that the hash values output in different consoles are different. This is because the Python interpreter adds a random salt to the string hash function each time it starts up. This approach effectively prevents HashDoS attacks and enhances the security of the hash algorithm.

","path":["Chapter 6. Hashing","6.3   Hash Algorithm"],"tags":[]},{"location":"chapter_hashing/hash_collision/","level":1,"title":"6.2   Hash Collision","text":"

The previous section mentioned that, in most cases, the input space of a hash function is much larger than the output space, so theoretically, hash collisions are inevitable. For example, if the input space is all integers and the output space is the array capacity size, then multiple integers will inevitably be mapped to the same bucket index.

Hash collisions can lead to incorrect query results, severely impacting the usability of the hash table. To address this issue, whenever a hash collision occurs, we can perform hash table expansion until the collision disappears. This approach is simple, straightforward, and effective, but it is very inefficient because hash table expansion involves a large amount of data migration and hash value recalculation. To improve efficiency, we can adopt the following strategies:

  1. Improve the hash table data structure so that the hash table can function normally when hash collisions occur.
  2. Only expand when necessary, that is, only when hash collisions are severe.

The main methods for improving the structure of hash tables include \"separate chaining\" and \"open addressing\".

","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#621-separate-chaining","level":2,"title":"6.2.1   Separate Chaining","text":"

In the original hash table, each bucket can store only one key-value pair. Separate chaining converts a single element into a linked list, treating key-value pairs as linked list nodes and storing all colliding key-value pairs in the same linked list. Figure 6-5 shows an example of a separate chaining hash table.

Figure 6-5   Separate chaining hash table

The operations of a hash table implemented with separate chaining have changed as follows:

  • Querying elements: Input key, obtain the bucket index through the hash function, then access the head node of the linked list, then traverse the linked list and compare key to find the target key-value pair.
  • Adding elements: First access the linked list head node through the hash function, then append the node (key-value pair) to the linked list.
  • Deleting elements: Access the head of the linked list based on the result of the hash function, then traverse the linked list to find the target node and delete it.

Separate chaining has the following limitations:

  • Increased Space Usage: The linked list contains node pointers, which consume more memory space than arrays.
  • Reduced Query Efficiency: This is because linear traversal of the linked list is required to find the corresponding element.

The code below provides a simple implementation of a separate chaining hash table, with two things to note:

  • Lists (dynamic arrays) are used instead of linked lists to simplify the code. In this setup, the hash table (array) contains multiple buckets, each of which is a list.
  • This implementation includes a hash table expansion method. When the load factor exceeds \\(\\frac{2}{3}\\), we expand the hash table to \\(2\\) times its original size.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map_chaining.py
class HashMapChaining:\n    \"\"\"Hash table with separate chaining\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self.size = 0  # Number of key-value pairs\n        self.capacity = 4  # Hash table capacity\n        self.load_thres = 2.0 / 3.0  # Load factor threshold for triggering expansion\n        self.extend_ratio = 2  # Expansion multiplier\n        self.buckets = [[] for _ in range(self.capacity)]  # Bucket array\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        return key % self.capacity\n\n    def load_factor(self) -> float:\n        \"\"\"Load factor\"\"\"\n        return self.size / self.capacity\n\n    def get(self, key: int) -> str | None:\n        \"\"\"Query operation\"\"\"\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse bucket, if key is found, return corresponding val\n        for pair in bucket:\n            if pair.key == key:\n                return pair.val\n        # If key is not found, return None\n        return None\n\n    def put(self, key: int, val: str):\n        \"\"\"Add operation\"\"\"\n        # When load factor exceeds threshold, perform expansion\n        if self.load_factor() > self.load_thres:\n            self.extend()\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse bucket, if specified key is encountered, update corresponding val and return\n        for pair in bucket:\n            if pair.key == key:\n                pair.val = val\n                return\n        # If key does not exist, append key-value pair to the end\n        pair = Pair(key, val)\n        bucket.append(pair)\n        self.size += 1\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse bucket and remove key-value pair from it\n        for pair in bucket:\n            if pair.key == key:\n                bucket.remove(pair)\n                self.size -= 1\n                break\n\n    def extend(self):\n        \"\"\"Expand hash table\"\"\"\n        # Temporarily store the original hash table\n        buckets = self.buckets\n        # Initialize expanded new hash table\n        self.capacity *= self.extend_ratio\n        self.buckets = [[] for _ in range(self.capacity)]\n        self.size = 0\n        # Move key-value pairs from original hash table to new hash table\n        for bucket in buckets:\n            for pair in bucket:\n                self.put(pair.key, pair.val)\n\n    def print(self):\n        \"\"\"Print hash table\"\"\"\n        for bucket in self.buckets:\n            res = []\n            for pair in bucket:\n                res.append(str(pair.key) + \" -> \" + pair.val)\n            print(res)\n
hash_map_chaining.cpp
/* Hash table with separate chaining */\nclass HashMapChaining {\n  private:\n    int size;                       // Number of key-value pairs\n    int capacity;                   // Hash table capacity\n    double loadThres;               // Load factor threshold for triggering expansion\n    int extendRatio;                // Expansion multiplier\n    vector<vector<Pair *>> buckets; // Bucket array\n\n  public:\n    /* Constructor */\n    HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) {\n        buckets.resize(capacity);\n    }\n\n    /* Destructor */\n    ~HashMapChaining() {\n        for (auto &bucket : buckets) {\n            for (Pair *pair : bucket) {\n                // Free memory\n                delete pair;\n            }\n        }\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double)size / (double)capacity;\n    }\n\n    /* Query operation */\n    string get(int key) {\n        int index = hashFunc(key);\n        // Traverse bucket, if key is found, return corresponding val\n        for (Pair *pair : buckets[index]) {\n            if (pair->key == key) {\n                return pair->val;\n            }\n        }\n        // Return empty string if key not found\n        return \"\";\n    }\n\n    /* Add operation */\n    void put(int key, string val) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        int index = hashFunc(key);\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for (Pair *pair : buckets[index]) {\n            if (pair->key == key) {\n                pair->val = val;\n                return;\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        buckets[index].push_back(new Pair(key, val));\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        auto &bucket = buckets[index];\n        // Traverse bucket and remove key-value pair from it\n        for (int i = 0; i < bucket.size(); i++) {\n            if (bucket[i]->key == key) {\n                Pair *tmp = bucket[i];\n                bucket.erase(bucket.begin() + i); // Remove key-value pair from it\n                delete tmp;                       // Free memory\n                size--;\n                return;\n            }\n        }\n    }\n\n    /* Expand hash table */\n    void extend() {\n        // Temporarily store the original hash table\n        vector<vector<Pair *>> bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets.clear();\n        buckets.resize(capacity);\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (auto &bucket : bucketsTmp) {\n            for (Pair *pair : bucket) {\n                put(pair->key, pair->val);\n                // Free memory\n                delete pair;\n            }\n        }\n    }\n\n    /* Print hash table */\n    void print() {\n        for (auto &bucket : buckets) {\n            cout << \"[\";\n            for (Pair *pair : bucket) {\n                cout << pair->key << \" -> \" << pair->val << \", \";\n            }\n            cout << \"]\\n\";\n        }\n    }\n};\n
hash_map_chaining.java
/* Hash table with separate chaining */\nclass HashMapChaining {\n    int size; // Number of key-value pairs\n    int capacity; // Hash table capacity\n    double loadThres; // Load factor threshold for triggering expansion\n    int extendRatio; // Expansion multiplier\n    List<List<Pair>> buckets; // Bucket array\n\n    /* Constructor */\n    public HashMapChaining() {\n        size = 0;\n        capacity = 4;\n        loadThres = 2.0 / 3.0;\n        extendRatio = 2;\n        buckets = new ArrayList<>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.add(new ArrayList<>());\n        }\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double) size / capacity;\n    }\n\n    /* Query operation */\n    String get(int key) {\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse bucket, if key is found, return corresponding val\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                return pair.val;\n            }\n        }\n        // If key is not found, return null\n        return null;\n    }\n\n    /* Add operation */\n    void put(int key, String val) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        Pair pair = new Pair(key, val);\n        bucket.add(pair);\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse bucket and remove key-value pair from it\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                bucket.remove(pair);\n                size--;\n                break;\n            }\n        }\n    }\n\n    /* Expand hash table */\n    void extend() {\n        // Temporarily store the original hash table\n        List<List<Pair>> bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets = new ArrayList<>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.add(new ArrayList<>());\n        }\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (List<Pair> bucket : bucketsTmp) {\n            for (Pair pair : bucket) {\n                put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    void print() {\n        for (List<Pair> bucket : buckets) {\n            List<String> res = new ArrayList<>();\n            for (Pair pair : bucket) {\n                res.add(pair.key + \" -> \" + pair.val);\n            }\n            System.out.println(res);\n        }\n    }\n}\n
hash_map_chaining.cs
/* Hash table with separate chaining */\nclass HashMapChaining {\n    int size; // Number of key-value pairs\n    int capacity; // Hash table capacity\n    double loadThres; // Load factor threshold for triggering expansion\n    int extendRatio; // Expansion multiplier\n    List<List<Pair>> buckets; // Bucket array\n\n    /* Constructor */\n    public HashMapChaining() {\n        size = 0;\n        capacity = 4;\n        loadThres = 2.0 / 3.0;\n        extendRatio = 2;\n        buckets = new List<List<Pair>>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.Add([]);\n        }\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double LoadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Query operation */\n    public string? Get(int key) {\n        int index = HashFunc(key);\n        // Traverse bucket, if key is found, return corresponding val\n        foreach (Pair pair in buckets[index]) {\n            if (pair.key == key) {\n                return pair.val;\n            }\n        }\n        // If key is not found, return null\n        return null;\n    }\n\n    /* Add operation */\n    public void Put(int key, string val) {\n        // When load factor exceeds threshold, perform expansion\n        if (LoadFactor() > loadThres) {\n            Extend();\n        }\n        int index = HashFunc(key);\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        foreach (Pair pair in buckets[index]) {\n            if (pair.key == key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        buckets[index].Add(new Pair(key, val));\n        size++;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        int index = HashFunc(key);\n        // Traverse bucket and remove key-value pair from it\n        foreach (Pair pair in buckets[index].ToList()) {\n            if (pair.key == key) {\n                buckets[index].Remove(pair);\n                size--;\n                break;\n            }\n        }\n    }\n\n    /* Expand hash table */\n    void Extend() {\n        // Temporarily store the original hash table\n        List<List<Pair>> bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets = new List<List<Pair>>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.Add([]);\n        }\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        foreach (List<Pair> bucket in bucketsTmp) {\n            foreach (Pair pair in bucket) {\n                Put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    public void Print() {\n        foreach (List<Pair> bucket in buckets) {\n            List<string> res = [];\n            foreach (Pair pair in bucket) {\n                res.Add(pair.key + \" -> \" + pair.val);\n            }\n            foreach (string kv in res) {\n                Console.WriteLine(kv);\n            }\n        }\n    }\n}\n
hash_map_chaining.go
/* Hash table with separate chaining */\ntype hashMapChaining struct {\n    size        int      // Number of key-value pairs\n    capacity    int      // Hash table capacity\n    loadThres   float64  // Load factor threshold for triggering expansion\n    extendRatio int      // Expansion multiplier\n    buckets     [][]pair // Bucket array\n}\n\n/* Constructor */\nfunc newHashMapChaining() *hashMapChaining {\n    buckets := make([][]pair, 4)\n    for i := 0; i < 4; i++ {\n        buckets[i] = make([]pair, 0)\n    }\n    return &hashMapChaining{\n        size:        0,\n        capacity:    4,\n        loadThres:   2.0 / 3.0,\n        extendRatio: 2,\n        buckets:     buckets,\n    }\n}\n\n/* Hash function */\nfunc (m *hashMapChaining) hashFunc(key int) int {\n    return key % m.capacity\n}\n\n/* Load factor */\nfunc (m *hashMapChaining) loadFactor() float64 {\n    return float64(m.size) / float64(m.capacity)\n}\n\n/* Query operation */\nfunc (m *hashMapChaining) get(key int) string {\n    idx := m.hashFunc(key)\n    bucket := m.buckets[idx]\n    // Traverse bucket, if key is found, return corresponding val\n    for _, p := range bucket {\n        if p.key == key {\n            return p.val\n        }\n    }\n    // Return empty string if key not found\n    return \"\"\n}\n\n/* Add operation */\nfunc (m *hashMapChaining) put(key int, val string) {\n    // When load factor exceeds threshold, perform expansion\n    if m.loadFactor() > m.loadThres {\n        m.extend()\n    }\n    idx := m.hashFunc(key)\n    // Traverse bucket, if specified key is encountered, update corresponding val and return\n    for i := range m.buckets[idx] {\n        if m.buckets[idx][i].key == key {\n            m.buckets[idx][i].val = val\n            return\n        }\n    }\n    // If key does not exist, append key-value pair to the end\n    p := pair{\n        key: key,\n        val: val,\n    }\n    m.buckets[idx] = append(m.buckets[idx], p)\n    m.size += 1\n}\n\n/* Remove operation */\nfunc (m *hashMapChaining) remove(key int) {\n    idx := m.hashFunc(key)\n    // Traverse bucket and remove key-value pair from it\n    for i, p := range m.buckets[idx] {\n        if p.key == key {\n            // Slice deletion\n            m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...)\n            m.size -= 1\n            break\n        }\n    }\n}\n\n/* Expand hash table */\nfunc (m *hashMapChaining) extend() {\n    // Temporarily store the original hash table\n    tmpBuckets := make([][]pair, len(m.buckets))\n    for i := 0; i < len(m.buckets); i++ {\n        tmpBuckets[i] = make([]pair, len(m.buckets[i]))\n        copy(tmpBuckets[i], m.buckets[i])\n    }\n    // Initialize expanded new hash table\n    m.capacity *= m.extendRatio\n    m.buckets = make([][]pair, m.capacity)\n    for i := 0; i < m.capacity; i++ {\n        m.buckets[i] = make([]pair, 0)\n    }\n    m.size = 0\n    // Move key-value pairs from original hash table to new hash table\n    for _, bucket := range tmpBuckets {\n        for _, p := range bucket {\n            m.put(p.key, p.val)\n        }\n    }\n}\n\n/* Print hash table */\nfunc (m *hashMapChaining) print() {\n    var builder strings.Builder\n\n    for _, bucket := range m.buckets {\n        builder.WriteString(\"[\")\n        for _, p := range bucket {\n            builder.WriteString(strconv.Itoa(p.key) + \" -> \" + p.val + \" \")\n        }\n        builder.WriteString(\"]\")\n        fmt.Println(builder.String())\n        builder.Reset()\n    }\n}\n
hash_map_chaining.swift
/* Hash table with separate chaining */\nclass HashMapChaining {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    var loadThres: Double // Load factor threshold for triggering expansion\n    var extendRatio: Int // Expansion multiplier\n    var buckets: [[Pair]] // Bucket array\n\n    /* Constructor */\n    init() {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = Array(repeating: [], count: capacity)\n    }\n\n    /* Hash function */\n    func hashFunc(key: Int) -> Int {\n        key % capacity\n    }\n\n    /* Load factor */\n    func loadFactor() -> Double {\n        Double(size) / Double(capacity)\n    }\n\n    /* Query operation */\n    func get(key: Int) -> String? {\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse bucket, if key is found, return corresponding val\n        for pair in bucket {\n            if pair.key == key {\n                return pair.val\n            }\n        }\n        // Return nil if key not found\n        return nil\n    }\n\n    /* Add operation */\n    func put(key: Int, val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if loadFactor() > loadThres {\n            extend()\n        }\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for pair in bucket {\n            if pair.key == key {\n                pair.val = val\n                return\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        let pair = Pair(key: key, val: val)\n        buckets[index].append(pair)\n        size += 1\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse bucket and remove key-value pair from it\n        for (pairIndex, pair) in bucket.enumerated() {\n            if pair.key == key {\n                buckets[index].remove(at: pairIndex)\n                size -= 1\n                break\n            }\n        }\n    }\n\n    /* Expand hash table */\n    func extend() {\n        // Temporarily store the original hash table\n        let bucketsTmp = buckets\n        // Initialize expanded new hash table\n        capacity *= extendRatio\n        buckets = Array(repeating: [], count: capacity)\n        size = 0\n        // Move key-value pairs from original hash table to new hash table\n        for bucket in bucketsTmp {\n            for pair in bucket {\n                put(key: pair.key, val: pair.val)\n            }\n        }\n    }\n\n    /* Print hash table */\n    func print() {\n        for bucket in buckets {\n            let res = bucket.map { \"\\($0.key) -> \\($0.val)\" }\n            Swift.print(res)\n        }\n    }\n}\n
hash_map_chaining.js
/* Hash table with separate chaining */\nclass HashMapChaining {\n    #size; // Number of key-value pairs\n    #capacity; // Hash table capacity\n    #loadThres; // Load factor threshold for triggering expansion\n    #extendRatio; // Expansion multiplier\n    #buckets; // Bucket array\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0;\n        this.#capacity = 4;\n        this.#loadThres = 2.0 / 3.0;\n        this.#extendRatio = 2;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor() {\n        return this.#size / this.#capacity;\n    }\n\n    /* Query operation */\n    get(key) {\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse bucket, if key is found, return corresponding val\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                return pair.val;\n            }\n        }\n        // If key is not found, return null\n        return null;\n    }\n\n    /* Add operation */\n    put(key, val) {\n        // When load factor exceeds threshold, perform expansion\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        const pair = new Pair(key, val);\n        bucket.push(pair);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key) {\n        const index = this.#hashFunc(key);\n        let bucket = this.#buckets[index];\n        // Traverse bucket and remove key-value pair from it\n        for (let i = 0; i < bucket.length; i++) {\n            if (bucket[i].key === key) {\n                bucket.splice(i, 1);\n                this.#size--;\n                break;\n            }\n        }\n    }\n\n    /* Expand hash table */\n    #extend() {\n        // Temporarily store the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize expanded new hash table\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n        this.#size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (const bucket of bucketsTmp) {\n            for (const pair of bucket) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    print() {\n        for (const bucket of this.#buckets) {\n            let res = [];\n            for (const pair of bucket) {\n                res.push(pair.key + ' -> ' + pair.val);\n            }\n            console.log(res);\n        }\n    }\n}\n
hash_map_chaining.ts
/* Hash table with separate chaining */\nclass HashMapChaining {\n    #size: number; // Number of key-value pairs\n    #capacity: number; // Hash table capacity\n    #loadThres: number; // Load factor threshold for triggering expansion\n    #extendRatio: number; // Expansion multiplier\n    #buckets: Pair[][]; // Bucket array\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0;\n        this.#capacity = 4;\n        this.#loadThres = 2.0 / 3.0;\n        this.#extendRatio = 2;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n    }\n\n    /* Hash function */\n    #hashFunc(key: number): number {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor(): number {\n        return this.#size / this.#capacity;\n    }\n\n    /* Query operation */\n    get(key: number): string | null {\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse bucket, if key is found, return corresponding val\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                return pair.val;\n            }\n        }\n        // If key is not found, return null\n        return null;\n    }\n\n    /* Add operation */\n    put(key: number, val: string): void {\n        // When load factor exceeds threshold, perform expansion\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        const pair = new Pair(key, val);\n        bucket.push(pair);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key: number): void {\n        const index = this.#hashFunc(key);\n        let bucket = this.#buckets[index];\n        // Traverse bucket and remove key-value pair from it\n        for (let i = 0; i < bucket.length; i++) {\n            if (bucket[i].key === key) {\n                bucket.splice(i, 1);\n                this.#size--;\n                break;\n            }\n        }\n    }\n\n    /* Expand hash table */\n    #extend(): void {\n        // Temporarily store the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize expanded new hash table\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n        this.#size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (const bucket of bucketsTmp) {\n            for (const pair of bucket) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    print(): void {\n        for (const bucket of this.#buckets) {\n            let res = [];\n            for (const pair of bucket) {\n                res.push(pair.key + ' -> ' + pair.val);\n            }\n            console.log(res);\n        }\n    }\n}\n
hash_map_chaining.dart
/* Hash table with separate chaining */\nclass HashMapChaining {\n  late int size; // Number of key-value pairs\n  late int capacity; // Hash table capacity\n  late double loadThres; // Load factor threshold for triggering expansion\n  late int extendRatio; // Expansion multiplier\n  late List<List<Pair>> buckets; // Bucket array\n\n  /* Constructor */\n  HashMapChaining() {\n    size = 0;\n    capacity = 4;\n    loadThres = 2.0 / 3.0;\n    extendRatio = 2;\n    buckets = List.generate(capacity, (_) => []);\n  }\n\n  /* Hash function */\n  int hashFunc(int key) {\n    return key % capacity;\n  }\n\n  /* Load factor */\n  double loadFactor() {\n    return size / capacity;\n  }\n\n  /* Query operation */\n  String? get(int key) {\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse bucket, if key is found, return corresponding val\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        return pair.val;\n      }\n    }\n    // If key is not found, return null\n    return null;\n  }\n\n  /* Add operation */\n  void put(int key, String val) {\n    // When load factor exceeds threshold, perform expansion\n    if (loadFactor() > loadThres) {\n      extend();\n    }\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse bucket, if specified key is encountered, update corresponding val and return\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        pair.val = val;\n        return;\n      }\n    }\n    // If key does not exist, append key-value pair to the end\n    Pair pair = Pair(key, val);\n    bucket.add(pair);\n    size++;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse bucket and remove key-value pair from it\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        bucket.remove(pair);\n        size--;\n        break;\n      }\n    }\n  }\n\n  /* Expand hash table */\n  void extend() {\n    // Temporarily store the original hash table\n    List<List<Pair>> bucketsTmp = buckets;\n    // Initialize expanded new hash table\n    capacity *= extendRatio;\n    buckets = List.generate(capacity, (_) => []);\n    size = 0;\n    // Move key-value pairs from original hash table to new hash table\n    for (List<Pair> bucket in bucketsTmp) {\n      for (Pair pair in bucket) {\n        put(pair.key, pair.val);\n      }\n    }\n  }\n\n  /* Print hash table */\n  void printHashMap() {\n    for (List<Pair> bucket in buckets) {\n      List<String> res = [];\n      for (Pair pair in bucket) {\n        res.add(\"${pair.key} -> ${pair.val}\");\n      }\n      print(res);\n    }\n  }\n}\n
hash_map_chaining.rs
/* Hash table with separate chaining */\nstruct HashMapChaining {\n    size: usize,\n    capacity: usize,\n    load_thres: f32,\n    extend_ratio: usize,\n    buckets: Vec<Vec<Pair>>,\n}\n\nimpl HashMapChaining {\n    /* Constructor */\n    fn new() -> Self {\n        Self {\n            size: 0,\n            capacity: 4,\n            load_thres: 2.0 / 3.0,\n            extend_ratio: 2,\n            buckets: vec![vec![]; 4],\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        key as usize % self.capacity\n    }\n\n    /* Load factor */\n    fn load_factor(&self) -> f32 {\n        self.size as f32 / self.capacity as f32\n    }\n\n    /* Remove operation */\n    fn remove(&mut self, key: i32) -> Option<String> {\n        let index = self.hash_func(key);\n\n        // Traverse bucket and remove key-value pair from it\n        for (i, p) in self.buckets[index].iter_mut().enumerate() {\n            if p.key == key {\n                let pair = self.buckets[index].remove(i);\n                self.size -= 1;\n                return Some(pair.val);\n            }\n        }\n\n        // If key is not found, return None\n        None\n    }\n\n    /* Expand hash table */\n    fn extend(&mut self) {\n        // Temporarily store the original hash table\n        let buckets_tmp = std::mem::take(&mut self.buckets);\n\n        // Initialize expanded new hash table\n        self.capacity *= self.extend_ratio;\n        self.buckets = vec![Vec::new(); self.capacity as usize];\n        self.size = 0;\n\n        // Move key-value pairs from original hash table to new hash table\n        for bucket in buckets_tmp {\n            for pair in bucket {\n                self.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    fn print(&self) {\n        for bucket in &self.buckets {\n            let mut res = Vec::new();\n            for pair in bucket {\n                res.push(format!(\"{} -> {}\", pair.key, pair.val));\n            }\n            println!(\"{:?}\", res);\n        }\n    }\n\n    /* Add operation */\n    fn put(&mut self, key: i32, val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if self.load_factor() > self.load_thres {\n            self.extend();\n        }\n\n        let index = self.hash_func(key);\n\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for pair in self.buckets[index].iter_mut() {\n            if pair.key == key {\n                pair.val = val;\n                return;\n            }\n        }\n\n        // If key does not exist, append key-value pair to the end\n        let pair = Pair { key, val };\n        self.buckets[index].push(pair);\n        self.size += 1;\n    }\n\n    /* Query operation */\n    fn get(&self, key: i32) -> Option<&str> {\n        let index = self.hash_func(key);\n\n        // Traverse bucket, if key is found, return corresponding val\n        for pair in self.buckets[index].iter() {\n            if pair.key == key {\n                return Some(&pair.val);\n            }\n        }\n\n        // If key is not found, return None\n        None\n    }\n}\n
hash_map_chaining.c
/* Linked list node */\ntypedef struct Node {\n    Pair *pair;\n    struct Node *next;\n} Node;\n\n/* Hash table with separate chaining */\ntypedef struct {\n    int size;         // Number of key-value pairs\n    int capacity;     // Hash table capacity\n    double loadThres; // Load factor threshold for triggering expansion\n    int extendRatio;  // Expansion multiplier\n    Node **buckets;   // Bucket array\n} HashMapChaining;\n\n/* Constructor */\nHashMapChaining *newHashMapChaining() {\n    HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining));\n    hashMap->size = 0;\n    hashMap->capacity = 4;\n    hashMap->loadThres = 2.0 / 3.0;\n    hashMap->extendRatio = 2;\n    hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *));\n    for (int i = 0; i < hashMap->capacity; i++) {\n        hashMap->buckets[i] = NULL;\n    }\n    return hashMap;\n}\n\n/* Destructor */\nvoid delHashMapChaining(HashMapChaining *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Node *cur = hashMap->buckets[i];\n        while (cur) {\n            Node *tmp = cur;\n            cur = cur->next;\n            free(tmp->pair);\n            free(tmp);\n        }\n    }\n    free(hashMap->buckets);\n    free(hashMap);\n}\n\n/* Hash function */\nint hashFunc(HashMapChaining *hashMap, int key) {\n    return key % hashMap->capacity;\n}\n\n/* Load factor */\ndouble loadFactor(HashMapChaining *hashMap) {\n    return (double)hashMap->size / (double)hashMap->capacity;\n}\n\n/* Query operation */\nchar *get(HashMapChaining *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    // Traverse bucket, if key is found, return corresponding val\n    Node *cur = hashMap->buckets[index];\n    while (cur) {\n        if (cur->pair->key == key) {\n            return cur->pair->val;\n        }\n        cur = cur->next;\n    }\n    return \"\"; // Return empty string if key not found\n}\n\n/* Add operation */\nvoid put(HashMapChaining *hashMap, int key, const char *val) {\n    // When load factor exceeds threshold, perform expansion\n    if (loadFactor(hashMap) > hashMap->loadThres) {\n        extend(hashMap);\n    }\n    int index = hashFunc(hashMap, key);\n    // Traverse bucket, if specified key is encountered, update corresponding val and return\n    Node *cur = hashMap->buckets[index];\n    while (cur) {\n        if (cur->pair->key == key) {\n            strcpy(cur->pair->val, val); // If specified key is found, update corresponding val and return\n            return;\n        }\n        cur = cur->next;\n    }\n    // If key not found, add key-value pair to list head\n    Pair *newPair = (Pair *)malloc(sizeof(Pair));\n    newPair->key = key;\n    strcpy(newPair->val, val);\n    Node *newNode = (Node *)malloc(sizeof(Node));\n    newNode->pair = newPair;\n    newNode->next = hashMap->buckets[index];\n    hashMap->buckets[index] = newNode;\n    hashMap->size++;\n}\n\n/* Expand hash table */\nvoid extend(HashMapChaining *hashMap) {\n    // Temporarily store the original hash table\n    int oldCapacity = hashMap->capacity;\n    Node **oldBuckets = hashMap->buckets;\n    // Initialize expanded new hash table\n    hashMap->capacity *= hashMap->extendRatio;\n    hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *));\n    for (int i = 0; i < hashMap->capacity; i++) {\n        hashMap->buckets[i] = NULL;\n    }\n    hashMap->size = 0;\n    // Move key-value pairs from original hash table to new hash table\n    for (int i = 0; i < oldCapacity; i++) {\n        Node *cur = oldBuckets[i];\n        while (cur) {\n            put(hashMap, cur->pair->key, cur->pair->val);\n            Node *temp = cur;\n            cur = cur->next;\n            // Free memory\n            free(temp->pair);\n            free(temp);\n        }\n    }\n\n    free(oldBuckets);\n}\n\n/* Remove operation */\nvoid removeItem(HashMapChaining *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    Node *cur = hashMap->buckets[index];\n    Node *pre = NULL;\n    while (cur) {\n        if (cur->pair->key == key) {\n            // Remove key-value pair from it\n            if (pre) {\n                pre->next = cur->next;\n            } else {\n                hashMap->buckets[index] = cur->next;\n            }\n            // Free memory\n            free(cur->pair);\n            free(cur);\n            hashMap->size--;\n            return;\n        }\n        pre = cur;\n        cur = cur->next;\n    }\n}\n\n/* Print hash table */\nvoid print(HashMapChaining *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Node *cur = hashMap->buckets[i];\n        printf(\"[\");\n        while (cur) {\n            printf(\"%d -> %s, \", cur->pair->key, cur->pair->val);\n            cur = cur->next;\n        }\n        printf(\"]\\n\");\n    }\n}\n
hash_map_chaining.kt
/* Hash table with separate chaining */\nclass HashMapChaining {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    val loadThres: Double // Load factor threshold for triggering expansion\n    val extendRatio: Int // Expansion multiplier\n    var buckets: MutableList<MutableList<Pair>> // Bucket array\n\n    /* Constructor */\n    init {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = mutableListOf()\n        for (i in 0..<capacity) {\n            buckets.add(mutableListOf())\n        }\n    }\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        return key % capacity\n    }\n\n    /* Load factor */\n    fun loadFactor(): Double {\n        return (size / capacity).toDouble()\n    }\n\n    /* Query operation */\n    fun get(key: Int): String? {\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse bucket, if key is found, return corresponding val\n        for (pair in bucket) {\n            if (pair.key == key) return pair._val\n        }\n        // If key is not found, return null\n        return null\n    }\n\n    /* Add operation */\n    fun put(key: Int, _val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend()\n        }\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for (pair in bucket) {\n            if (pair.key == key) {\n                pair._val = _val\n                return\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        val pair = Pair(key, _val)\n        bucket.add(pair)\n        size++\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse bucket and remove key-value pair from it\n        for (pair in bucket) {\n            if (pair.key == key) {\n                bucket.remove(pair)\n                size--\n                break\n            }\n        }\n    }\n\n    /* Expand hash table */\n    fun extend() {\n        // Temporarily store the original hash table\n        val bucketsTmp = buckets\n        // Initialize expanded new hash table\n        capacity *= extendRatio\n        // mutablelist has no fixed size\n        buckets = mutableListOf()\n        for (i in 0..<capacity) {\n            buckets.add(mutableListOf())\n        }\n        size = 0\n        // Move key-value pairs from original hash table to new hash table\n        for (bucket in bucketsTmp) {\n            for (pair in bucket) {\n                put(pair.key, pair._val)\n            }\n        }\n    }\n\n    /* Print hash table */\n    fun print() {\n        for (bucket in buckets) {\n            val res = mutableListOf<String>()\n            for (pair in bucket) {\n                val k = pair.key\n                val v = pair._val\n                res.add(\"$k -> $v\")\n            }\n            println(res)\n        }\n    }\n}\n
hash_map_chaining.rb
### Hash map with chaining ###\nclass HashMapChaining\n  ### Constructor ###\n  def initialize\n    @size = 0 # Number of key-value pairs\n    @capacity = 4 # Hash table capacity\n    @load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion\n    @extend_ratio = 2 # Expansion multiplier\n    @buckets = Array.new(@capacity) { [] } # Bucket array\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    key % @capacity\n  end\n\n  ### Load factor ###\n  def load_factor\n    @size / @capacity\n  end\n\n  ### Query operation ###\n  def get(key)\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse bucket, if key is found, return corresponding val\n    for pair in bucket\n      return pair.val if pair.key == key\n    end\n    # Return nil if key not found\n    nil\n  end\n\n  ### Add operation ###\n  def put(key, val)\n    # When load factor exceeds threshold, perform expansion\n    extend if load_factor > @load_thres\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse bucket, if specified key is encountered, update corresponding val and return\n    for pair in bucket\n      if pair.key == key\n        pair.val = val\n        return\n      end\n    end\n    # If key does not exist, append key-value pair to the end\n    pair = Pair.new(key, val)\n    bucket << pair\n    @size += 1\n  end\n\n  ### Delete operation ###\n  def remove(key)\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse bucket and remove key-value pair from it\n    for pair in bucket\n      if pair.key == key\n        bucket.delete(pair)\n        @size -= 1\n        break\n      end\n    end\n  end\n\n  ### Expand hash table ###\n  def extend\n    # Temporarily store original hash table\n    buckets = @buckets\n    # Initialize expanded new hash table\n    @capacity *= @extend_ratio\n    @buckets = Array.new(@capacity) { [] }\n    @size = 0\n    # Move key-value pairs from original hash table to new hash table\n    for bucket in buckets\n      for pair in bucket\n        put(pair.key, pair.val)\n      end\n    end\n  end\n\n  ### Print hash table ###\n  def print\n    for bucket in @buckets\n      res = []\n      for pair in bucket\n        res << \"#{pair.key} -> #{pair.val}\"\n      end\n      pp res\n    end\n  end\nend\n

It's worth noting that when the linked list is very long, the query efficiency \\(O(n)\\) is poor. In this case, the list can be converted to an \"AVL tree\" or \"Red-Black tree\" to optimize the time complexity of the query operation to \\(O(\\log n)\\).

","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#622-open-addressing","level":2,"title":"6.2.2   Open Addressing","text":"

Open addressing does not introduce additional data structures but instead handles hash collisions through \"multiple probes\". The probing methods mainly include linear probing, quadratic probing, and double hashing.

Let's use linear probing as an example to introduce the mechanism of open addressing hash tables.

","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#1-linear-probing","level":3,"title":"1.   Linear Probing","text":"

Linear probing uses a fixed-step linear search for probing, and its operation method differs from ordinary hash tables.

  • Inserting elements: Calculate the bucket index using the hash function. If the bucket already contains an element, linearly traverse forward from the conflict position (usually with a step size of \\(1\\)) until an empty bucket is found, then insert the element.
  • Searching for elements: If a hash collision is encountered, use the same step size to linearly traverse forward until the corresponding element is found and return value; if an empty bucket is encountered, it means the target element is not in the hash table, so return None.

Figure 6-6 shows the distribution of key-value pairs in an open addressing (linear probing) hash table. According to this hash function, keys with the same last two digits will be mapped to the same bucket. Through linear probing, they are stored sequentially in that bucket and the buckets below it.

Figure 6-6   Distribution of key-value pairs in open addressing (linear probing) hash table

However, linear probing is prone to create \"clustering\". Specifically, the longer the continuously occupied positions in the array, the greater the probability of hash collisions occurring in these continuous positions, further promoting clustering growth at that position, forming a vicious cycle, and ultimately leading to degraded efficiency of insertion, deletion, query, and update operations.

It's important to note that we cannot directly delete elements in an open addressing hash table. Deleting an element creates an empty bucket None in the array. When searching for elements, if linear probing encounters this empty bucket, it will return, making the elements below this empty bucket inaccessible. The program may incorrectly assume these elements do not exist, as shown in Figure 6-7.

Figure 6-7   Query issues caused by deletion in open addressing

To solve this problem, we can adopt the lazy deletion mechanism: instead of directly removing elements from the hash table, use a constant TOMBSTONE to mark the bucket. In this mechanism, both None and TOMBSTONE represent empty buckets and can hold key-value pairs. However, when linear probing encounters TOMBSTONE, it should continue traversing since there may still be key-value pairs below it.

However, lazy deletion may accelerate the performance degradation of the hash table. Every deletion operation produces a deletion mark, and as TOMBSTONE increases, the search time will also increase because linear probing may need to skip multiple TOMBSTONE to find the target element.

To address this, consider recording the index of the first encountered TOMBSTONE during linear probing and swapping the searched target element with that TOMBSTONE. The benefit of doing this is that each time an element is queried or added, the element will be moved to a bucket closer to its ideal position (the starting point of probing), thereby optimizing query efficiency.

The code below implements an open addressing (linear probing) hash table with lazy deletion. To make better use of the hash table space, we treat the hash table as a \"circular array\". When going beyond the end of the array, we return to the beginning and continue traversing.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map_open_addressing.py
class HashMapOpenAddressing:\n    \"\"\"Hash table with open addressing\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self.size = 0  # Number of key-value pairs\n        self.capacity = 4  # Hash table capacity\n        self.load_thres = 2.0 / 3.0  # Load factor threshold for triggering expansion\n        self.extend_ratio = 2  # Expansion multiplier\n        self.buckets: list[Pair | None] = [None] * self.capacity  # Bucket array\n        self.TOMBSTONE = Pair(-1, \"-1\")  # Removal marker\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        return key % self.capacity\n\n    def load_factor(self) -> float:\n        \"\"\"Load factor\"\"\"\n        return self.size / self.capacity\n\n    def find_bucket(self, key: int) -> int:\n        \"\"\"Search for bucket index corresponding to key\"\"\"\n        index = self.hash_func(key)\n        first_tombstone = -1\n        # Linear probing, break when encountering an empty bucket\n        while self.buckets[index] is not None:\n            # If key is encountered, return the corresponding bucket index\n            if self.buckets[index].key == key:\n                # If a removal marker was encountered before, move the key-value pair to that index\n                if first_tombstone != -1:\n                    self.buckets[first_tombstone] = self.buckets[index]\n                    self.buckets[index] = self.TOMBSTONE\n                    return first_tombstone  # Return the moved bucket index\n                return index  # Return bucket index\n            # Record the first removal marker encountered\n            if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE:\n                first_tombstone = index\n            # Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % self.capacity\n        # If key does not exist, return the index for insertion\n        return index if first_tombstone == -1 else first_tombstone\n\n    def get(self, key: int) -> str:\n        \"\"\"Query operation\"\"\"\n        # Search for bucket index corresponding to key\n        index = self.find_bucket(key)\n        # If key-value pair is found, return corresponding val\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            return self.buckets[index].val\n        # If key-value pair does not exist, return None\n        return None\n\n    def put(self, key: int, val: str):\n        \"\"\"Add operation\"\"\"\n        # When load factor exceeds threshold, perform expansion\n        if self.load_factor() > self.load_thres:\n            self.extend()\n        # Search for bucket index corresponding to key\n        index = self.find_bucket(key)\n        # If key-value pair is found, overwrite val and return\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            self.buckets[index].val = val\n            return\n        # If key-value pair does not exist, add the key-value pair\n        self.buckets[index] = Pair(key, val)\n        self.size += 1\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        # Search for bucket index corresponding to key\n        index = self.find_bucket(key)\n        # If key-value pair is found, overwrite it with removal marker\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            self.buckets[index] = self.TOMBSTONE\n            self.size -= 1\n\n    def extend(self):\n        \"\"\"Expand hash table\"\"\"\n        # Temporarily store the original hash table\n        buckets_tmp = self.buckets\n        # Initialize expanded new hash table\n        self.capacity *= self.extend_ratio\n        self.buckets = [None] * self.capacity\n        self.size = 0\n        # Move key-value pairs from original hash table to new hash table\n        for pair in buckets_tmp:\n            if pair not in [None, self.TOMBSTONE]:\n                self.put(pair.key, pair.val)\n\n    def print(self):\n        \"\"\"Print hash table\"\"\"\n        for pair in self.buckets:\n            if pair is None:\n                print(\"None\")\n            elif pair is self.TOMBSTONE:\n                print(\"TOMBSTONE\")\n            else:\n                print(pair.key, \"->\", pair.val)\n
hash_map_open_addressing.cpp
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n  private:\n    int size;                             // Number of key-value pairs\n    int capacity = 4;                     // Hash table capacity\n    const double loadThres = 2.0 / 3.0;     // Load factor threshold for triggering expansion\n    const int extendRatio = 2;            // Expansion multiplier\n    vector<Pair *> buckets;               // Bucket array\n    Pair *TOMBSTONE = new Pair(-1, \"-1\"); // Removal marker\n\n  public:\n    /* Constructor */\n    HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) {\n    }\n\n    /* Destructor */\n    ~HashMapOpenAddressing() {\n        for (Pair *pair : buckets) {\n            if (pair != nullptr && pair != TOMBSTONE) {\n                delete pair;\n            }\n        }\n        delete TOMBSTONE;\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Search for bucket index corresponding to key */\n    int findBucket(int key) {\n        int index = hashFunc(key);\n        int firstTombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while (buckets[index] != nullptr) {\n            // If key is encountered, return the corresponding bucket index\n            if (buckets[index]->key == key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % capacity;\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Query operation */\n    string get(int key) {\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, return corresponding val\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            return buckets[index]->val;\n        }\n        // Return empty string if key-value pair does not exist\n        return \"\";\n    }\n\n    /* Add operation */\n    void put(int key, string val) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, overwrite val and return\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            buckets[index]->val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            delete buckets[index];\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Expand hash table */\n    void extend() {\n        // Temporarily store the original hash table\n        vector<Pair *> bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets = vector<Pair *>(capacity, nullptr);\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (Pair *pair : bucketsTmp) {\n            if (pair != nullptr && pair != TOMBSTONE) {\n                put(pair->key, pair->val);\n                delete pair;\n            }\n        }\n    }\n\n    /* Print hash table */\n    void print() {\n        for (Pair *pair : buckets) {\n            if (pair == nullptr) {\n                cout << \"nullptr\" << endl;\n            } else if (pair == TOMBSTONE) {\n                cout << \"TOMBSTONE\" << endl;\n            } else {\n                cout << pair->key << \" -> \" << pair->val << endl;\n            }\n        }\n    }\n};\n
hash_map_open_addressing.java
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private int size; // Number of key-value pairs\n    private int capacity = 4; // Hash table capacity\n    private final double loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion\n    private final int extendRatio = 2; // Expansion multiplier\n    private Pair[] buckets; // Bucket array\n    private final Pair TOMBSTONE = new Pair(-1, \"-1\"); // Removal marker\n\n    /* Constructor */\n    public HashMapOpenAddressing() {\n        size = 0;\n        buckets = new Pair[capacity];\n    }\n\n    /* Hash function */\n    private int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    private double loadFactor() {\n        return (double) size / capacity;\n    }\n\n    /* Search for bucket index corresponding to key */\n    private int findBucket(int key) {\n        int index = hashFunc(key);\n        int firstTombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while (buckets[index] != null) {\n            // If key is encountered, return the corresponding bucket index\n            if (buckets[index].key == key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % capacity;\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Query operation */\n    public String get(int key) {\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, return corresponding val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index].val;\n        }\n        // If key-value pair does not exist, return null\n        return null;\n    }\n\n    /* Add operation */\n    public void put(int key, String val) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index].val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    public void remove(int key) {\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Expand hash table */\n    private void extend() {\n        // Temporarily store the original hash table\n        Pair[] bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets = new Pair[capacity];\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (Pair pair : bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    public void print() {\n        for (Pair pair : buckets) {\n            if (pair == null) {\n                System.out.println(\"null\");\n            } else if (pair == TOMBSTONE) {\n                System.out.println(\"TOMBSTONE\");\n            } else {\n                System.out.println(pair.key + \" -> \" + pair.val);\n            }\n        }\n    }\n}\n
hash_map_open_addressing.cs
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    int size; // Number of key-value pairs\n    int capacity = 4; // Hash table capacity\n    double loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion\n    int extendRatio = 2; // Expansion multiplier\n    Pair[] buckets; // Bucket array\n    Pair TOMBSTONE = new(-1, \"-1\"); // Removal marker\n\n    /* Constructor */\n    public HashMapOpenAddressing() {\n        size = 0;\n        buckets = new Pair[capacity];\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double LoadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Search for bucket index corresponding to key */\n    int FindBucket(int key) {\n        int index = HashFunc(key);\n        int firstTombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while (buckets[index] != null) {\n            // If key is encountered, return the corresponding bucket index\n            if (buckets[index].key == key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % capacity;\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Query operation */\n    public string? Get(int key) {\n        // Search for bucket index corresponding to key\n        int index = FindBucket(key);\n        // If key-value pair is found, return corresponding val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index].val;\n        }\n        // If key-value pair does not exist, return null\n        return null;\n    }\n\n    /* Add operation */\n    public void Put(int key, string val) {\n        // When load factor exceeds threshold, perform expansion\n        if (LoadFactor() > loadThres) {\n            Extend();\n        }\n        // Search for bucket index corresponding to key\n        int index = FindBucket(key);\n        // If key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index].val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        // Search for bucket index corresponding to key\n        int index = FindBucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Expand hash table */\n    void Extend() {\n        // Temporarily store the original hash table\n        Pair[] bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets = new Pair[capacity];\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        foreach (Pair pair in bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                Put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    public void Print() {\n        foreach (Pair pair in buckets) {\n            if (pair == null) {\n                Console.WriteLine(\"null\");\n            } else if (pair == TOMBSTONE) {\n                Console.WriteLine(\"TOMBSTONE\");\n            } else {\n                Console.WriteLine(pair.key + \" -> \" + pair.val);\n            }\n        }\n    }\n}\n
hash_map_open_addressing.go
/* Hash table with open addressing */\ntype hashMapOpenAddressing struct {\n    size        int     // Number of key-value pairs\n    capacity    int     // Hash table capacity\n    loadThres   float64 // Load factor threshold for triggering expansion\n    extendRatio int     // Expansion multiplier\n    buckets     []*pair // Bucket array\n    TOMBSTONE   *pair   // Removal marker\n}\n\n/* Constructor */\nfunc newHashMapOpenAddressing() *hashMapOpenAddressing {\n    return &hashMapOpenAddressing{\n        size:        0,\n        capacity:    4,\n        loadThres:   2.0 / 3.0,\n        extendRatio: 2,\n        buckets:     make([]*pair, 4),\n        TOMBSTONE:   &pair{-1, \"-1\"},\n    }\n}\n\n/* Hash function */\nfunc (h *hashMapOpenAddressing) hashFunc(key int) int {\n    return key % h.capacity // Calculate hash value based on key\n}\n\n/* Load factor */\nfunc (h *hashMapOpenAddressing) loadFactor() float64 {\n    return float64(h.size) / float64(h.capacity) // Calculate current load factor\n}\n\n/* Search for bucket index corresponding to key */\nfunc (h *hashMapOpenAddressing) findBucket(key int) int {\n    index := h.hashFunc(key) // Get initial index\n    firstTombstone := -1     // Record position of first TOMBSTONE encountered\n    for h.buckets[index] != nil {\n        if h.buckets[index].key == key {\n            if firstTombstone != -1 {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                h.buckets[firstTombstone] = h.buckets[index]\n                h.buckets[index] = h.TOMBSTONE\n                return firstTombstone // Return the moved bucket index\n            }\n            return index // Return found index\n        }\n        if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE {\n            firstTombstone = index // Record position of first deletion marker encountered\n        }\n        index = (index + 1) % h.capacity // Linear probing, wrap around to head if past tail\n    }\n    // If key does not exist, return the index for insertion\n    if firstTombstone != -1 {\n        return firstTombstone\n    }\n    return index\n}\n\n/* Query operation */\nfunc (h *hashMapOpenAddressing) get(key int) string {\n    index := h.findBucket(key) // Search for bucket index corresponding to key\n    if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE {\n        return h.buckets[index].val // If key-value pair is found, return corresponding val\n    }\n    return \"\" // Return \"\" if key-value pair does not exist\n}\n\n/* Add operation */\nfunc (h *hashMapOpenAddressing) put(key int, val string) {\n    if h.loadFactor() > h.loadThres {\n        h.extend() // When load factor exceeds threshold, perform expansion\n    }\n    index := h.findBucket(key) // Search for bucket index corresponding to key\n    if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE {\n        h.buckets[index] = &pair{key, val} // If key-value pair does not exist, add the key-value pair\n        h.size++\n    } else {\n        h.buckets[index].val = val // If key-value pair found, overwrite val\n    }\n}\n\n/* Remove operation */\nfunc (h *hashMapOpenAddressing) remove(key int) {\n    index := h.findBucket(key) // Search for bucket index corresponding to key\n    if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE {\n        h.buckets[index] = h.TOMBSTONE // If key-value pair is found, overwrite it with removal marker\n        h.size--\n    }\n}\n\n/* Expand hash table */\nfunc (h *hashMapOpenAddressing) extend() {\n    oldBuckets := h.buckets               // Temporarily store the original hash table\n    h.capacity *= h.extendRatio           // Update capacity\n    h.buckets = make([]*pair, h.capacity) // Initialize expanded new hash table\n    h.size = 0                            // Reset size\n    // Move key-value pairs from original hash table to new hash table\n    for _, pair := range oldBuckets {\n        if pair != nil && pair != h.TOMBSTONE {\n            h.put(pair.key, pair.val)\n        }\n    }\n}\n\n/* Print hash table */\nfunc (h *hashMapOpenAddressing) print() {\n    for _, pair := range h.buckets {\n        if pair == nil {\n            fmt.Println(\"nil\")\n        } else if pair == h.TOMBSTONE {\n            fmt.Println(\"TOMBSTONE\")\n        } else {\n            fmt.Printf(\"%d -> %s\\n\", pair.key, pair.val)\n        }\n    }\n}\n
hash_map_open_addressing.swift
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    var loadThres: Double // Load factor threshold for triggering expansion\n    var extendRatio: Int // Expansion multiplier\n    var buckets: [Pair?] // Bucket array\n    var TOMBSTONE: Pair // Removal marker\n\n    /* Constructor */\n    init() {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = Array(repeating: nil, count: capacity)\n        TOMBSTONE = Pair(key: -1, val: \"-1\")\n    }\n\n    /* Hash function */\n    func hashFunc(key: Int) -> Int {\n        key % capacity\n    }\n\n    /* Load factor */\n    func loadFactor() -> Double {\n        Double(size) / Double(capacity)\n    }\n\n    /* Search for bucket index corresponding to key */\n    func findBucket(key: Int) -> Int {\n        var index = hashFunc(key: key)\n        var firstTombstone = -1\n        // Linear probing, break when encountering an empty bucket\n        while buckets[index] != nil {\n            // If key is encountered, return the corresponding bucket index\n            if buckets[index]!.key == key {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if firstTombstone != -1 {\n                    buckets[firstTombstone] = buckets[index]\n                    buckets[index] = TOMBSTONE\n                    return firstTombstone // Return the moved bucket index\n                }\n                return index // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if firstTombstone == -1 && buckets[index] == TOMBSTONE {\n                firstTombstone = index\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % capacity\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone\n    }\n\n    /* Query operation */\n    func get(key: Int) -> String? {\n        // Search for bucket index corresponding to key\n        let index = findBucket(key: key)\n        // If key-value pair is found, return corresponding val\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            return buckets[index]!.val\n        }\n        // If key-value pair does not exist, return null\n        return nil\n    }\n\n    /* Add operation */\n    func put(key: Int, val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if loadFactor() > loadThres {\n            extend()\n        }\n        // Search for bucket index corresponding to key\n        let index = findBucket(key: key)\n        // If key-value pair is found, overwrite val and return\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            buckets[index]!.val = val\n            return\n        }\n        // If key-value pair does not exist, add the key-value pair\n        buckets[index] = Pair(key: key, val: val)\n        size += 1\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        // Search for bucket index corresponding to key\n        let index = findBucket(key: key)\n        // If key-value pair is found, overwrite it with removal marker\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            buckets[index] = TOMBSTONE\n            size -= 1\n        }\n    }\n\n    /* Expand hash table */\n    func extend() {\n        // Temporarily store the original hash table\n        let bucketsTmp = buckets\n        // Initialize expanded new hash table\n        capacity *= extendRatio\n        buckets = Array(repeating: nil, count: capacity)\n        size = 0\n        // Move key-value pairs from original hash table to new hash table\n        for pair in bucketsTmp {\n            if let pair, pair != TOMBSTONE {\n                put(key: pair.key, val: pair.val)\n            }\n        }\n    }\n\n    /* Print hash table */\n    func print() {\n        for pair in buckets {\n            if pair == nil {\n                Swift.print(\"null\")\n            } else if pair == TOMBSTONE {\n                Swift.print(\"TOMBSTONE\")\n            } else {\n                Swift.print(\"\\(pair!.key) -> \\(pair!.val)\")\n            }\n        }\n    }\n}\n
hash_map_open_addressing.js
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    #size; // Number of key-value pairs\n    #capacity; // Hash table capacity\n    #loadThres; // Load factor threshold for triggering expansion\n    #extendRatio; // Expansion multiplier\n    #buckets; // Bucket array\n    #TOMBSTONE; // Removal marker\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0; // Number of key-value pairs\n        this.#capacity = 4; // Hash table capacity\n        this.#loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion\n        this.#extendRatio = 2; // Expansion multiplier\n        this.#buckets = Array(this.#capacity).fill(null); // Bucket array\n        this.#TOMBSTONE = new Pair(-1, '-1'); // Removal marker\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor() {\n        return this.#size / this.#capacity;\n    }\n\n    /* Search for bucket index corresponding to key */\n    #findBucket(key) {\n        let index = this.#hashFunc(key);\n        let firstTombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while (this.#buckets[index] !== null) {\n            // If key is encountered, return the corresponding bucket index\n            if (this.#buckets[index].key === key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone !== -1) {\n                    this.#buckets[firstTombstone] = this.#buckets[index];\n                    this.#buckets[index] = this.#TOMBSTONE;\n                    return firstTombstone; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (\n                firstTombstone === -1 &&\n                this.#buckets[index] === this.#TOMBSTONE\n            ) {\n                firstTombstone = index;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % this.#capacity;\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone === -1 ? index : firstTombstone;\n    }\n\n    /* Query operation */\n    get(key) {\n        // Search for bucket index corresponding to key\n        const index = this.#findBucket(key);\n        // If key-value pair is found, return corresponding val\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            return this.#buckets[index].val;\n        }\n        // If key-value pair does not exist, return null\n        return null;\n    }\n\n    /* Add operation */\n    put(key, val) {\n        // When load factor exceeds threshold, perform expansion\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        // Search for bucket index corresponding to key\n        const index = this.#findBucket(key);\n        // If key-value pair is found, overwrite val and return\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            this.#buckets[index].val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        this.#buckets[index] = new Pair(key, val);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key) {\n        // Search for bucket index corresponding to key\n        const index = this.#findBucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            this.#buckets[index] = this.#TOMBSTONE;\n            this.#size--;\n        }\n    }\n\n    /* Expand hash table */\n    #extend() {\n        // Temporarily store the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize expanded new hash table\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = Array(this.#capacity).fill(null);\n        this.#size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (const pair of bucketsTmp) {\n            if (pair !== null && pair !== this.#TOMBSTONE) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    print() {\n        for (const pair of this.#buckets) {\n            if (pair === null) {\n                console.log('null');\n            } else if (pair === this.#TOMBSTONE) {\n                console.log('TOMBSTONE');\n            } else {\n                console.log(pair.key + ' -> ' + pair.val);\n            }\n        }\n    }\n}\n
hash_map_open_addressing.ts
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private size: number; // Number of key-value pairs\n    private capacity: number; // Hash table capacity\n    private loadThres: number; // Load factor threshold for triggering expansion\n    private extendRatio: number; // Expansion multiplier\n    private buckets: Array<Pair | null>; // Bucket array\n    private TOMBSTONE: Pair; // Removal marker\n\n    /* Constructor */\n    constructor() {\n        this.size = 0; // Number of key-value pairs\n        this.capacity = 4; // Hash table capacity\n        this.loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion\n        this.extendRatio = 2; // Expansion multiplier\n        this.buckets = Array(this.capacity).fill(null); // Bucket array\n        this.TOMBSTONE = new Pair(-1, '-1'); // Removal marker\n    }\n\n    /* Hash function */\n    private hashFunc(key: number): number {\n        return key % this.capacity;\n    }\n\n    /* Load factor */\n    private loadFactor(): number {\n        return this.size / this.capacity;\n    }\n\n    /* Search for bucket index corresponding to key */\n    private findBucket(key: number): number {\n        let index = this.hashFunc(key);\n        let firstTombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while (this.buckets[index] !== null) {\n            // If key is encountered, return the corresponding bucket index\n            if (this.buckets[index]!.key === key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone !== -1) {\n                    this.buckets[firstTombstone] = this.buckets[index];\n                    this.buckets[index] = this.TOMBSTONE;\n                    return firstTombstone; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (\n                firstTombstone === -1 &&\n                this.buckets[index] === this.TOMBSTONE\n            ) {\n                firstTombstone = index;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % this.capacity;\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone === -1 ? index : firstTombstone;\n    }\n\n    /* Query operation */\n    get(key: number): string | null {\n        // Search for bucket index corresponding to key\n        const index = this.findBucket(key);\n        // If key-value pair is found, return corresponding val\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            return this.buckets[index]!.val;\n        }\n        // If key-value pair does not exist, return null\n        return null;\n    }\n\n    /* Add operation */\n    put(key: number, val: string): void {\n        // When load factor exceeds threshold, perform expansion\n        if (this.loadFactor() > this.loadThres) {\n            this.extend();\n        }\n        // Search for bucket index corresponding to key\n        const index = this.findBucket(key);\n        // If key-value pair is found, overwrite val and return\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            this.buckets[index]!.val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        this.buckets[index] = new Pair(key, val);\n        this.size++;\n    }\n\n    /* Remove operation */\n    remove(key: number): void {\n        // Search for bucket index corresponding to key\n        const index = this.findBucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            this.buckets[index] = this.TOMBSTONE;\n            this.size--;\n        }\n    }\n\n    /* Expand hash table */\n    private extend(): void {\n        // Temporarily store the original hash table\n        const bucketsTmp = this.buckets;\n        // Initialize expanded new hash table\n        this.capacity *= this.extendRatio;\n        this.buckets = Array(this.capacity).fill(null);\n        this.size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (const pair of bucketsTmp) {\n            if (pair !== null && pair !== this.TOMBSTONE) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    print(): void {\n        for (const pair of this.buckets) {\n            if (pair === null) {\n                console.log('null');\n            } else if (pair === this.TOMBSTONE) {\n                console.log('TOMBSTONE');\n            } else {\n                console.log(pair.key + ' -> ' + pair.val);\n            }\n        }\n    }\n}\n
hash_map_open_addressing.dart
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n  late int _size; // Number of key-value pairs\n  int _capacity = 4; // Hash table capacity\n  double _loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion\n  int _extendRatio = 2; // Expansion multiplier\n  late List<Pair?> _buckets; // Bucket array\n  Pair _TOMBSTONE = Pair(-1, \"-1\"); // Removal marker\n\n  /* Constructor */\n  HashMapOpenAddressing() {\n    _size = 0;\n    _buckets = List.generate(_capacity, (index) => null);\n  }\n\n  /* Hash function */\n  int hashFunc(int key) {\n    return key % _capacity;\n  }\n\n  /* Load factor */\n  double loadFactor() {\n    return _size / _capacity;\n  }\n\n  /* Search for bucket index corresponding to key */\n  int findBucket(int key) {\n    int index = hashFunc(key);\n    int firstTombstone = -1;\n    // Linear probing, break when encountering an empty bucket\n    while (_buckets[index] != null) {\n      // If key is encountered, return the corresponding bucket index\n      if (_buckets[index]!.key == key) {\n        // If a removal marker was encountered before, move the key-value pair to that index\n        if (firstTombstone != -1) {\n          _buckets[firstTombstone] = _buckets[index];\n          _buckets[index] = _TOMBSTONE;\n          return firstTombstone; // Return the moved bucket index\n        }\n        return index; // Return bucket index\n      }\n      // Record the first removal marker encountered\n      if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) {\n        firstTombstone = index;\n      }\n      // Calculate bucket index, wrap around to the head if past the tail\n      index = (index + 1) % _capacity;\n    }\n    // If key does not exist, return the index for insertion\n    return firstTombstone == -1 ? index : firstTombstone;\n  }\n\n  /* Query operation */\n  String? get(int key) {\n    // Search for bucket index corresponding to key\n    int index = findBucket(key);\n    // If key-value pair is found, return corresponding val\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      return _buckets[index]!.val;\n    }\n    // If key-value pair does not exist, return null\n    return null;\n  }\n\n  /* Add operation */\n  void put(int key, String val) {\n    // When load factor exceeds threshold, perform expansion\n    if (loadFactor() > _loadThres) {\n      extend();\n    }\n    // Search for bucket index corresponding to key\n    int index = findBucket(key);\n    // If key-value pair is found, overwrite val and return\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      _buckets[index]!.val = val;\n      return;\n    }\n    // If key-value pair does not exist, add the key-value pair\n    _buckets[index] = new Pair(key, val);\n    _size++;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    // Search for bucket index corresponding to key\n    int index = findBucket(key);\n    // If key-value pair is found, overwrite it with removal marker\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      _buckets[index] = _TOMBSTONE;\n      _size--;\n    }\n  }\n\n  /* Expand hash table */\n  void extend() {\n    // Temporarily store the original hash table\n    List<Pair?> bucketsTmp = _buckets;\n    // Initialize expanded new hash table\n    _capacity *= _extendRatio;\n    _buckets = List.generate(_capacity, (index) => null);\n    _size = 0;\n    // Move key-value pairs from original hash table to new hash table\n    for (Pair? pair in bucketsTmp) {\n      if (pair != null && pair != _TOMBSTONE) {\n        put(pair.key, pair.val);\n      }\n    }\n  }\n\n  /* Print hash table */\n  void printHashMap() {\n    for (Pair? pair in _buckets) {\n      if (pair == null) {\n        print(\"null\");\n      } else if (pair == _TOMBSTONE) {\n        print(\"TOMBSTONE\");\n      } else {\n        print(\"${pair.key} -> ${pair.val}\");\n      }\n    }\n  }\n}\n
hash_map_open_addressing.rs
/* Hash table with open addressing */\nstruct HashMapOpenAddressing {\n    size: usize,                // Number of key-value pairs\n    capacity: usize,            // Hash table capacity\n    load_thres: f64,            // Load factor threshold for triggering expansion\n    extend_ratio: usize,        // Expansion multiplier\n    buckets: Vec<Option<Pair>>, // Bucket array\n    TOMBSTONE: Option<Pair>,    // Removal marker\n}\n\nimpl HashMapOpenAddressing {\n    /* Constructor */\n    fn new() -> Self {\n        Self {\n            size: 0,\n            capacity: 4,\n            load_thres: 2.0 / 3.0,\n            extend_ratio: 2,\n            buckets: vec![None; 4],\n            TOMBSTONE: Some(Pair {\n                key: -1,\n                val: \"-1\".to_string(),\n            }),\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        (key % self.capacity as i32) as usize\n    }\n\n    /* Load factor */\n    fn load_factor(&self) -> f64 {\n        self.size as f64 / self.capacity as f64\n    }\n\n    /* Search for bucket index corresponding to key */\n    fn find_bucket(&mut self, key: i32) -> usize {\n        let mut index = self.hash_func(key);\n        let mut first_tombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while self.buckets[index].is_some() {\n            // If key is found, return corresponding bucket index\n            if self.buckets[index].as_ref().unwrap().key == key {\n                // If deletion marker was encountered before, move key-value pair to that index\n                if first_tombstone != -1 {\n                    self.buckets[first_tombstone as usize] = self.buckets[index].take();\n                    self.buckets[index] = self.TOMBSTONE.clone();\n                    return first_tombstone as usize; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE {\n                first_tombstone = index as i32;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % self.capacity;\n        }\n        // If key does not exist, return the index for insertion\n        if first_tombstone == -1 {\n            index\n        } else {\n            first_tombstone as usize\n        }\n    }\n\n    /* Query operation */\n    fn get(&mut self, key: i32) -> Option<&str> {\n        // Search for bucket index corresponding to key\n        let index = self.find_bucket(key);\n        // If key-value pair is found, return corresponding val\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            return self.buckets[index].as_ref().map(|pair| &pair.val as &str);\n        }\n        // If key-value pair does not exist, return null\n        None\n    }\n\n    /* Add operation */\n    fn put(&mut self, key: i32, val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if self.load_factor() > self.load_thres {\n            self.extend();\n        }\n        // Search for bucket index corresponding to key\n        let index = self.find_bucket(key);\n        // If key-value pair is found, overwrite val and return\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            self.buckets[index].as_mut().unwrap().val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        self.buckets[index] = Some(Pair { key, val });\n        self.size += 1;\n    }\n\n    /* Remove operation */\n    fn remove(&mut self, key: i32) {\n        // Search for bucket index corresponding to key\n        let index = self.find_bucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            self.buckets[index] = self.TOMBSTONE.clone();\n            self.size -= 1;\n        }\n    }\n\n    /* Expand hash table */\n    fn extend(&mut self) {\n        // Temporarily store the original hash table\n        let buckets_tmp = self.buckets.clone();\n        // Initialize expanded new hash table\n        self.capacity *= self.extend_ratio;\n        self.buckets = vec![None; self.capacity];\n        self.size = 0;\n\n        // Move key-value pairs from original hash table to new hash table\n        for pair in buckets_tmp {\n            if pair.is_none() || pair == self.TOMBSTONE {\n                continue;\n            }\n            let pair = pair.unwrap();\n\n            self.put(pair.key, pair.val);\n        }\n    }\n    /* Print hash table */\n    fn print(&self) {\n        for pair in &self.buckets {\n            if pair.is_none() {\n                println!(\"null\");\n            } else if pair == &self.TOMBSTONE {\n                println!(\"TOMBSTONE\");\n            } else {\n                let pair = pair.as_ref().unwrap();\n                println!(\"{} -> {}\", pair.key, pair.val);\n            }\n        }\n    }\n}\n
hash_map_open_addressing.c
/* Hash table with open addressing */\ntypedef struct {\n    int size;         // Number of key-value pairs\n    int capacity;     // Hash table capacity\n    double loadThres; // Load factor threshold for triggering expansion\n    int extendRatio;  // Expansion multiplier\n    Pair **buckets;   // Bucket array\n    Pair *TOMBSTONE;  // Removal marker\n} HashMapOpenAddressing;\n\n/* Constructor */\nHashMapOpenAddressing *newHashMapOpenAddressing() {\n    HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing));\n    hashMap->size = 0;\n    hashMap->capacity = 4;\n    hashMap->loadThres = 2.0 / 3.0;\n    hashMap->extendRatio = 2;\n    hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));\n    hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair));\n    hashMap->TOMBSTONE->key = -1;\n    hashMap->TOMBSTONE->val = \"-1\";\n\n    return hashMap;\n}\n\n/* Destructor */\nvoid delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Pair *pair = hashMap->buckets[i];\n        if (pair != NULL && pair != hashMap->TOMBSTONE) {\n            free(pair->val);\n            free(pair);\n        }\n    }\n    free(hashMap->buckets);\n    free(hashMap->TOMBSTONE);\n    free(hashMap);\n}\n\n/* Hash function */\nint hashFunc(HashMapOpenAddressing *hashMap, int key) {\n    return key % hashMap->capacity;\n}\n\n/* Load factor */\ndouble loadFactor(HashMapOpenAddressing *hashMap) {\n    return (double)hashMap->size / (double)hashMap->capacity;\n}\n\n/* Search for bucket index corresponding to key */\nint findBucket(HashMapOpenAddressing *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    int firstTombstone = -1;\n    // Linear probing, break when encountering an empty bucket\n    while (hashMap->buckets[index] != NULL) {\n        // If key is encountered, return the corresponding bucket index\n        if (hashMap->buckets[index]->key == key) {\n            // If a removal marker was encountered before, move the key-value pair to that index\n            if (firstTombstone != -1) {\n                hashMap->buckets[firstTombstone] = hashMap->buckets[index];\n                hashMap->buckets[index] = hashMap->TOMBSTONE;\n                return firstTombstone; // Return the moved bucket index\n            }\n            return index; // Return bucket index\n        }\n        // Record the first removal marker encountered\n        if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) {\n            firstTombstone = index;\n        }\n        // Calculate bucket index, wrap around to the head if past the tail\n        index = (index + 1) % hashMap->capacity;\n    }\n    // If key does not exist, return the index for insertion\n    return firstTombstone == -1 ? index : firstTombstone;\n}\n\n/* Query operation */\nchar *get(HashMapOpenAddressing *hashMap, int key) {\n    // Search for bucket index corresponding to key\n    int index = findBucket(hashMap, key);\n    // If key-value pair is found, return corresponding val\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        return hashMap->buckets[index]->val;\n    }\n    // Return empty string if key-value pair does not exist\n    return \"\";\n}\n\n/* Add operation */\nvoid put(HashMapOpenAddressing *hashMap, int key, char *val) {\n    // When load factor exceeds threshold, perform expansion\n    if (loadFactor(hashMap) > hashMap->loadThres) {\n        extend(hashMap);\n    }\n    // Search for bucket index corresponding to key\n    int index = findBucket(hashMap, key);\n    // If key-value pair is found, overwrite val and return\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        free(hashMap->buckets[index]->val);\n        hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1));\n        strcpy(hashMap->buckets[index]->val, val);\n        hashMap->buckets[index]->val[strlen(val)] = '\\0';\n        return;\n    }\n    // If key-value pair does not exist, add the key-value pair\n    Pair *pair = (Pair *)malloc(sizeof(Pair));\n    pair->key = key;\n    pair->val = (char *)malloc(sizeof(strlen(val) + 1));\n    strcpy(pair->val, val);\n    pair->val[strlen(val)] = '\\0';\n\n    hashMap->buckets[index] = pair;\n    hashMap->size++;\n}\n\n/* Remove operation */\nvoid removeItem(HashMapOpenAddressing *hashMap, int key) {\n    // Search for bucket index corresponding to key\n    int index = findBucket(hashMap, key);\n    // If key-value pair is found, overwrite it with removal marker\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        Pair *pair = hashMap->buckets[index];\n        free(pair->val);\n        free(pair);\n        hashMap->buckets[index] = hashMap->TOMBSTONE;\n        hashMap->size--;\n    }\n}\n\n/* Expand hash table */\nvoid extend(HashMapOpenAddressing *hashMap) {\n    // Temporarily store the original hash table\n    Pair **bucketsTmp = hashMap->buckets;\n    int oldCapacity = hashMap->capacity;\n    // Initialize expanded new hash table\n    hashMap->capacity *= hashMap->extendRatio;\n    hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));\n    hashMap->size = 0;\n    // Move key-value pairs from original hash table to new hash table\n    for (int i = 0; i < oldCapacity; i++) {\n        Pair *pair = bucketsTmp[i];\n        if (pair != NULL && pair != hashMap->TOMBSTONE) {\n            put(hashMap, pair->key, pair->val);\n            free(pair->val);\n            free(pair);\n        }\n    }\n    free(bucketsTmp);\n}\n\n/* Print hash table */\nvoid print(HashMapOpenAddressing *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Pair *pair = hashMap->buckets[i];\n        if (pair == NULL) {\n            printf(\"NULL\\n\");\n        } else if (pair == hashMap->TOMBSTONE) {\n            printf(\"TOMBSTONE\\n\");\n        } else {\n            printf(\"%d -> %s\\n\", pair->key, pair->val);\n        }\n    }\n}\n
hash_map_open_addressing.kt
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private var size: Int               // Number of key-value pairs\n    private var capacity: Int           // Hash table capacity\n    private val loadThres: Double       // Load factor threshold for triggering expansion\n    private val extendRatio: Int        // Expansion multiplier\n    private var buckets: Array<Pair?>   // Bucket array\n    private val TOMBSTONE: Pair         // Removal marker\n\n    /* Constructor */\n    init {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = arrayOfNulls(capacity)\n        TOMBSTONE = Pair(-1, \"-1\")\n    }\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        return key % capacity\n    }\n\n    /* Load factor */\n    fun loadFactor(): Double {\n        return (size / capacity).toDouble()\n    }\n\n    /* Search for bucket index corresponding to key */\n    fun findBucket(key: Int): Int {\n        var index = hashFunc(key)\n        var firstTombstone = -1\n        // Linear probing, break when encountering an empty bucket\n        while (buckets[index] != null) {\n            // If key is encountered, return the corresponding bucket index\n            if (buckets[index]?.key == key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index]\n                    buckets[index] = TOMBSTONE\n                    return firstTombstone // Return the moved bucket index\n                }\n                return index // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % capacity\n        }\n        // If key does not exist, return the index for insertion\n        return if (firstTombstone == -1) index else firstTombstone\n    }\n\n    /* Query operation */\n    fun get(key: Int): String? {\n        // Search for bucket index corresponding to key\n        val index = findBucket(key)\n        // If key-value pair is found, return corresponding val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index]?._val\n        }\n        // If key-value pair does not exist, return null\n        return null\n    }\n\n    /* Add operation */\n    fun put(key: Int, _val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend()\n        }\n        // Search for bucket index corresponding to key\n        val index = findBucket(key)\n        // If key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index]!!._val = _val\n            return\n        }\n        // If key-value pair does not exist, add the key-value pair\n        buckets[index] = Pair(key, _val)\n        size++\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        // Search for bucket index corresponding to key\n        val index = findBucket(key)\n        // If key-value pair is found, overwrite it with removal marker\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE\n            size--\n        }\n    }\n\n    /* Expand hash table */\n    fun extend() {\n        // Temporarily store the original hash table\n        val bucketsTmp = buckets\n        // Initialize expanded new hash table\n        capacity *= extendRatio\n        buckets = arrayOfNulls(capacity)\n        size = 0\n        // Move key-value pairs from original hash table to new hash table\n        for (pair in bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                put(pair.key, pair._val)\n            }\n        }\n    }\n\n    /* Print hash table */\n    fun print() {\n        for (pair in buckets) {\n            if (pair == null) {\n                println(\"null\")\n            } else if (pair == TOMBSTONE) {\n                println(\"TOMESTOME\")\n            } else {\n                println(\"${pair.key} -> ${pair._val}\")\n            }\n        }\n    }\n}\n
hash_map_open_addressing.rb
### Hash map with open addressing ###\nclass HashMapOpenAddressing\n  TOMBSTONE = Pair.new(-1, '-1') # Removal marker\n\n  ### Constructor ###\n  def initialize\n    @size = 0 # Number of key-value pairs\n    @capacity = 4 # Hash table capacity\n    @load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion\n    @extend_ratio = 2 # Expansion multiplier\n    @buckets = Array.new(@capacity) # Bucket array\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    key % @capacity\n  end\n\n  ### Load factor ###\n  def load_factor\n    @size / @capacity\n  end\n\n  ### Search bucket index for key ###\n  def find_bucket(key)\n    index = hash_func(key)\n    first_tombstone = -1\n    # Linear probing, break when encountering an empty bucket\n    while !@buckets[index].nil?\n      # If key is encountered, return the corresponding bucket index\n      if @buckets[index].key == key\n        # If a removal marker was encountered before, move the key-value pair to that index\n        if first_tombstone != -1\n          @buckets[first_tombstone] = @buckets[index]\n          @buckets[index] = TOMBSTONE\n          return first_tombstone # Return the moved bucket index\n        end\n        return index # Return bucket index\n      end\n      # Record the first removal marker encountered\n      first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE\n      # Calculate bucket index, wrap around to the head if past the tail\n      index = (index + 1) % @capacity\n    end\n    # If key does not exist, return the index for insertion\n    first_tombstone == -1 ? index : first_tombstone\n  end\n\n  ### Query operation ###\n  def get(key)\n    # Search for bucket index corresponding to key\n    index = find_bucket(key)\n    # If key-value pair is found, return corresponding val\n    return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index])\n    # Return nil if key-value pair does not exist\n    nil\n  end\n\n  ### Add operation ###\n  def put(key, val)\n    # When load factor exceeds threshold, perform expansion\n    extend if load_factor > @load_thres\n    # Search for bucket index corresponding to key\n    index = find_bucket(key)\n    # If key-value pair found, overwrite val and return\n    unless [nil, TOMBSTONE].include?(@buckets[index])\n      @buckets[index].val = val\n      return\n    end\n    # If key-value pair does not exist, add the key-value pair\n    @buckets[index] = Pair.new(key, val)\n    @size += 1\n  end\n\n  ### Delete operation ###\n  def remove(key)\n    # Search for bucket index corresponding to key\n    index = find_bucket(key)\n    # If key-value pair is found, overwrite it with removal marker\n    unless [nil, TOMBSTONE].include?(@buckets[index])\n      @buckets[index] = TOMBSTONE\n      @size -= 1\n    end\n  end\n\n  ### Expand hash table ###\n  def extend\n    # Temporarily store the original hash table\n    buckets_tmp = @buckets\n    # Initialize expanded new hash table\n    @capacity *= @extend_ratio\n    @buckets = Array.new(@capacity)\n    @size = 0\n    # Move key-value pairs from original hash table to new hash table\n    for pair in buckets_tmp\n      put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair)\n    end\n  end\n\n  ### Print hash table ###\n  def print\n    for pair in @buckets\n      if pair.nil?\n        puts \"Nil\"\n      elsif pair == TOMBSTONE\n        puts \"TOMBSTONE\"\n      else\n        puts \"#{pair.key} -> #{pair.val}\"\n      end\n    end\n  end\nend\n
","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#2-quadratic-probing","level":3,"title":"2.   Quadratic Probing","text":"

Quadratic probing is similar to linear probing and is one of the common strategies for open addressing. When a collision occurs, quadratic probing does not simply skip a fixed number of steps but skips a number of steps equal to the \"square of the number of probes\", i.e., \\(1, 4, 9, \\dots\\) steps.

Quadratic probing has the following advantages:

  • Quadratic probing attempts to alleviate the clustering effect of linear probing by skipping distances equal to the square of the probe count.
  • Quadratic probing skips larger distances to find empty positions, which helps to distribute data more evenly.

However, quadratic probing is not perfect:

  • Clustering still exists, i.e., some positions are more likely to be occupied than others.
  • Due to the growth of squares, quadratic probing may not probe the entire hash table, meaning that even if there are empty buckets in the hash table, quadratic probing may not be able to access them.
","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#3-double-hashing","level":3,"title":"3.   Double Hashing","text":"

As the name suggests, the double hashing method uses multiple hash functions \\(f_1(x)\\), \\(f_2(x)\\), \\(f_3(x)\\), \\(\\dots\\) for probing.

  • Inserting elements: If hash function \\(f_1(x)\\) encounters a conflict, try \\(f_2(x)\\), and so on, until an empty position is found and the element is inserted.
  • Searching for elements: Search in the same order of hash functions until the target element is found and return it; if an empty position is encountered or all hash functions have been tried, it indicates the element is not in the hash table, then return None.

Compared to linear probing, the double hashing method is less prone to clustering, but multiple hash functions introduce additional computational overhead.

Tip

Please note that open addressing (linear probing, quadratic probing, and double hashing) hash tables all have the problem of \"cannot directly delete elements\".

","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#623-choice-of-programming-languages","level":2,"title":"6.2.3   Choice of Programming Languages","text":"

Different programming languages adopt different hash table implementation strategies. Here are a few examples:

  • Python uses open addressing. The dict dictionary uses pseudo-random numbers for probing.
  • Java uses separate chaining. Since JDK 1.8, when the array length in HashMap reaches 64 and the length of a linked list reaches 8, the linked list is converted to a red-black tree to improve search performance.
  • Go uses separate chaining. Go stipulates that each bucket can store up to 8 key-value pairs, and if the capacity is exceeded, an overflow bucket is linked; when there are too many overflow buckets, a special equal-capacity expansion operation is performed to ensure performance.
","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_map/","level":1,"title":"6.1   Hash Table","text":"

A hash table, also known as a hash map, establishes a mapping between keys key and values value, enabling efficient element retrieval. Specifically, when we input a key key into a hash table, we can retrieve the corresponding value value in \\(O(1)\\) time.

As shown in Figure 6-1, given \\(n\\) students, each with two pieces of data: \"name\" and \"student ID\". If we want to implement a query function that \"inputs a student ID and returns the corresponding name\", we can use the hash table shown below.

Figure 6-1   Abstract representation of a hash table

In addition to hash tables, arrays and linked lists can also implement query functionality. Their efficiency comparison is shown in the following table.

  • Adding elements: Simply add elements to the end of the array (linked list), using \\(O(1)\\) time.
  • Querying elements: Since the array (linked list) is unordered, all elements need to be traversed, using \\(O(n)\\) time.
  • Deleting elements: The element must first be located, then deleted from the array (linked list), using \\(O(n)\\) time.

Table 6-1   Comparison of element query efficiency

Array Linked List Hash Table Find element \\(O(n)\\) \\(O(n)\\) \\(O(1)\\) Add element \\(O(1)\\) \\(O(1)\\) \\(O(1)\\) Delete element \\(O(n)\\) \\(O(n)\\) \\(O(1)\\)

As observed, the time complexity for insertion, deletion, search, and modification operations in a hash table is \\(O(1)\\), which is very efficient.

","path":["Chapter 6. Hashing","6.1   Hash Table"],"tags":[]},{"location":"chapter_hashing/hash_map/#611-common-hash-table-operations","level":2,"title":"6.1.1   Common Hash Table Operations","text":"

Common operations on hash tables include: initialization, query operations, adding key-value pairs, and deleting key-value pairs. Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map.py
# Initialize hash table\nhmap: dict = {}\n\n# Add operation\n# Add key-value pair (key, value) to hash table\nhmap[12836] = \"XiaoHa\"\nhmap[15937] = \"XiaoLuo\"\nhmap[16750] = \"XiaoSuan\"\nhmap[13276] = \"XiaoFa\"\nhmap[10583] = \"XiaoYa\"\n\n# Query operation\n# Input key into hash table to get value\nname: str = hmap[15937]\n\n# Delete operation\n# Delete key-value pair (key, value) from hash table\nhmap.pop(10583)\n
hash_map.cpp
/* Initialize hash table */\nunordered_map<int, string> map;\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap[12836] = \"XiaoHa\";\nmap[15937] = \"XiaoLuo\";\nmap[16750] = \"XiaoSuan\";\nmap[13276] = \"XiaoFa\";\nmap[10583] = \"XiaoYa\";\n\n/* Query operation */\n// Input key into hash table to get value\nstring name = map[15937];\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.erase(10583);\n
hash_map.java
/* Initialize hash table */\nMap<Integer, String> map = new HashMap<>();\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap.put(12836, \"XiaoHa\");\nmap.put(15937, \"XiaoLuo\");\nmap.put(16750, \"XiaoSuan\");\nmap.put(13276, \"XiaoFa\");\nmap.put(10583, \"XiaoYa\");\n\n/* Query operation */\n// Input key into hash table to get value\nString name = map.get(15937);\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.remove(10583);\n
hash_map.cs
/* Initialize hash table */\nDictionary<int, string> map = new() {\n    /* Add operation */\n    // Add key-value pair (key, value) to hash table\n    { 12836, \"XiaoHa\" },\n    { 15937, \"XiaoLuo\" },\n    { 16750, \"XiaoSuan\" },\n    { 13276, \"XiaoFa\" },\n    { 10583, \"XiaoYa\" }\n};\n\n/* Query operation */\n// Input key into hash table to get value\nstring name = map[15937];\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.Remove(10583);\n
hash_map_test.go
/* Initialize hash table */\nhmap := make(map[int]string)\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nhmap[12836] = \"XiaoHa\"\nhmap[15937] = \"XiaoLuo\"\nhmap[16750] = \"XiaoSuan\"\nhmap[13276] = \"XiaoFa\"\nhmap[10583] = \"XiaoYa\"\n\n/* Query operation */\n// Input key into hash table to get value\nname := hmap[15937]\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\ndelete(hmap, 10583)\n
hash_map.swift
/* Initialize hash table */\nvar map: [Int: String] = [:]\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap[12836] = \"XiaoHa\"\nmap[15937] = \"XiaoLuo\"\nmap[16750] = \"XiaoSuan\"\nmap[13276] = \"XiaoFa\"\nmap[10583] = \"XiaoYa\"\n\n/* Query operation */\n// Input key into hash table to get value\nlet name = map[15937]!\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.removeValue(forKey: 10583)\n
hash_map.js
/* Initialize hash table */\nconst map = new Map();\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap.set(12836, 'XiaoHa');\nmap.set(15937, 'XiaoLuo');\nmap.set(16750, 'XiaoSuan');\nmap.set(13276, 'XiaoFa');\nmap.set(10583, 'XiaoYa');\n\n/* Query operation */\n// Input key into hash table to get value\nlet name = map.get(15937);\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.delete(10583);\n
hash_map.ts
/* Initialize hash table */\nconst map = new Map<number, string>();\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap.set(12836, 'XiaoHa');\nmap.set(15937, 'XiaoLuo');\nmap.set(16750, 'XiaoSuan');\nmap.set(13276, 'XiaoFa');\nmap.set(10583, 'XiaoYa');\nconsole.info('\\nAfter adding, hash table is\\nKey -> Value');\nconsole.info(map);\n\n/* Query operation */\n// Input key into hash table to get value\nlet name = map.get(15937);\nconsole.info('\\nInput student ID 15937, queried name ' + name);\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.delete(10583);\nconsole.info('\\nAfter deleting 10583, hash table is\\nKey -> Value');\nconsole.info(map);\n
hash_map.dart
/* Initialize hash table */\nMap<int, String> map = {};\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap[12836] = \"XiaoHa\";\nmap[15937] = \"XiaoLuo\";\nmap[16750] = \"XiaoSuan\";\nmap[13276] = \"XiaoFa\";\nmap[10583] = \"XiaoYa\";\n\n/* Query operation */\n// Input key into hash table to get value\nString name = map[15937];\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.remove(10583);\n
hash_map.rs
use std::collections::HashMap;\n\n/* Initialize hash table */\nlet mut map: HashMap<i32, String> = HashMap::new();\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap.insert(12836, \"XiaoHa\".to_string());\nmap.insert(15937, \"XiaoLuo\".to_string());\nmap.insert(16750, \"XiaoSuan\".to_string());\nmap.insert(13279, \"XiaoFa\".to_string());\nmap.insert(10583, \"XiaoYa\".to_string());\n\n/* Query operation */\n// Input key into hash table to get value\nlet _name: Option<&String> = map.get(&15937);\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nlet _removed_value: Option<String> = map.remove(&10583);\n
hash_map.c
// C does not provide a built-in hash table\n
hash_map.kt
/* Initialize hash table */\nval map = HashMap<Int,String>()\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap[12836] = \"XiaoHa\"\nmap[15937] = \"XiaoLuo\"\nmap[16750] = \"XiaoSuan\"\nmap[13276] = \"XiaoFa\"\nmap[10583] = \"XiaoYa\"\n\n/* Query operation */\n// Input key into hash table to get value\nval name = map[15937]\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.remove(10583)\n
hash_map.rb
# Initialize hash table\nhmap = {}\n\n# Add operation\n# Add key-value pair (key, value) to hash table\nhmap[12836] = \"XiaoHa\"\nhmap[15937] = \"XiaoLuo\"\nhmap[16750] = \"XiaoSuan\"\nhmap[13276] = \"XiaoFa\"\nhmap[10583] = \"XiaoYa\"\n\n# Query operation\n# Input key into hash table to get value\nname = hmap[15937]\n\n# Delete operation\n# Delete key-value pair (key, value) from hash table\nhmap.delete(10583)\n
Visualized Execution

https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

There are three common ways to traverse a hash table: traversing key-value pairs, traversing keys, and traversing values. Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map.py
# Traverse hash table\n# Traverse key-value pairs key->value\nfor key, value in hmap.items():\n    print(key, \"->\", value)\n# Traverse keys only\nfor key in hmap.keys():\n    print(key)\n# Traverse values only\nfor value in hmap.values():\n    print(value)\n
hash_map.cpp
/* Traverse hash table */\n// Traverse key-value pairs key->value\nfor (auto kv: map) {\n    cout << kv.first << \" -> \" << kv.second << endl;\n}\n// Traverse using iterator key->value\nfor (auto iter = map.begin(); iter != map.end(); iter++) {\n    cout << iter->first << \"->\" << iter->second << endl;\n}\n
hash_map.java
/* Traverse hash table */\n// Traverse key-value pairs key->value\nfor (Map.Entry<Integer, String> kv: map.entrySet()) {\n    System.out.println(kv.getKey() + \" -> \" + kv.getValue());\n}\n// Traverse keys only\nfor (int key: map.keySet()) {\n    System.out.println(key);\n}\n// Traverse values only\nfor (String val: map.values()) {\n    System.out.println(val);\n}\n
hash_map.cs
/* Traverse hash table */\n// Traverse key-value pairs Key->Value\nforeach (var kv in map) {\n    Console.WriteLine(kv.Key + \" -> \" + kv.Value);\n}\n// Traverse keys only\nforeach (int key in map.Keys) {\n    Console.WriteLine(key);\n}\n// Traverse values only\nforeach (string val in map.Values) {\n    Console.WriteLine(val);\n}\n
hash_map_test.go
/* Traverse hash table */\n// Traverse key-value pairs key->value\nfor key, value := range hmap {\n    fmt.Println(key, \"->\", value)\n}\n// Traverse keys only\nfor key := range hmap {\n    fmt.Println(key)\n}\n// Traverse values only\nfor _, value := range hmap {\n    fmt.Println(value)\n}\n
hash_map.swift
/* Traverse hash table */\n// Traverse key-value pairs Key->Value\nfor (key, value) in map {\n    print(\"\\(key) -> \\(value)\")\n}\n// Traverse keys only\nfor key in map.keys {\n    print(key)\n}\n// Traverse values only\nfor value in map.values {\n    print(value)\n}\n
hash_map.js
/* Traverse hash table */\nconsole.info('\\nTraverse key-value pairs Key->Value');\nfor (const [k, v] of map.entries()) {\n    console.info(k + ' -> ' + v);\n}\nconsole.info('\\nTraverse keys only Key');\nfor (const k of map.keys()) {\n    console.info(k);\n}\nconsole.info('\\nTraverse values only Value');\nfor (const v of map.values()) {\n    console.info(v);\n}\n
hash_map.ts
/* Traverse hash table */\nconsole.info('\\nTraverse key-value pairs Key->Value');\nfor (const [k, v] of map.entries()) {\n    console.info(k + ' -> ' + v);\n}\nconsole.info('\\nTraverse keys only Key');\nfor (const k of map.keys()) {\n    console.info(k);\n}\nconsole.info('\\nTraverse values only Value');\nfor (const v of map.values()) {\n    console.info(v);\n}\n
hash_map.dart
/* Traverse hash table */\n// Traverse key-value pairs Key->Value\nmap.forEach((key, value) {\n  print('$key -> $value');\n});\n\n// Traverse keys only\nmap.keys.forEach((key) {\n  print(key);\n});\n\n// Traverse values only\nmap.values.forEach((value) {\n  print(value);\n});\n
hash_map.rs
/* Traverse hash table */\n// Traverse key-value pairs Key->Value\nfor (key, value) in &map {\n    println!(\"{key} -> {value}\");\n}\n\n// Traverse keys only\nfor key in map.keys() {\n    println!(\"{key}\");\n}\n\n// Traverse values only\nfor value in map.values() {\n    println!(\"{value}\");\n}\n
hash_map.c
// C does not provide a built-in hash table\n
hash_map.kt
/* Traverse hash table */\n// Traverse key-value pairs key->value\nfor ((key, value) in map) {\n    println(\"$key -> $value\")\n}\n// Traverse keys only\nfor (key in map.keys) {\n    println(key)\n}\n// Traverse values only\nfor (_val in map.values) {\n    println(_val)\n}\n
hash_map.rb
# Traverse hash table\n# Traverse key-value pairs key->value\nhmap.entries.each { |key, value| puts \"#{key} -> #{value}\" }\n\n# Traverse keys only\nhmap.keys.each { |key| puts key }\n\n# Traverse values only\nhmap.values.each { |val| puts val }\n
Visualized Execution

https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

","path":["Chapter 6. Hashing","6.1   Hash Table"],"tags":[]},{"location":"chapter_hashing/hash_map/#612-simple-hash-table-implementation","level":2,"title":"6.1.2   Simple Hash Table Implementation","text":"

Let's first consider the simplest case: implementing a hash table using only an array. In a hash table, each empty position in the array is called a bucket, and each bucket can store a key-value pair. Therefore, the query operation is to find the bucket corresponding to key and retrieve the value from the bucket.

So how do we locate the corresponding bucket based on key? This is achieved through a hash function. The role of the hash function is to map a larger input space to a smaller output space. In a hash table, the input space is all keys, and the output space is all buckets (array indices). In other words, given a key, we can use the hash function to obtain the storage location of the key-value pair corresponding to that key in the array.

When inputting a key, the hash function's calculation process consists of the following two steps:

  1. Calculate the hash value through a hash algorithm hash().
  2. Take the modulo of the hash value by the number of buckets (array length) capacity to obtain the bucket (array index) index corresponding to that key.
index = hash(key) % capacity\n

Subsequently, we can use index to access the corresponding bucket in the hash table and retrieve the value.

Assuming the array length is capacity = 100 and the hash algorithm is hash(key) = key, the hash function becomes key % 100. Figure 6-2 shows the working principle of the hash function using key as student ID and value as name.

Figure 6-2   Working principle of hash function

The following code implements a simple hash table. Here, we encapsulate key and value into a class Pair to represent a key-value pair.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_hash_map.py
class Pair:\n    \"\"\"Key-value pair\"\"\"\n\n    def __init__(self, key: int, val: str):\n        self.key = key\n        self.val = val\n\nclass ArrayHashMap:\n    \"\"\"Hash table based on array implementation\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        # Initialize array with 100 buckets\n        self.buckets: list[Pair | None] = [None] * 100\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        index = key % 100\n        return index\n\n    def get(self, key: int) -> str | None:\n        \"\"\"Query operation\"\"\"\n        index: int = self.hash_func(key)\n        pair: Pair = self.buckets[index]\n        if pair is None:\n            return None\n        return pair.val\n\n    def put(self, key: int, val: str):\n        \"\"\"Add and update operation\"\"\"\n        pair = Pair(key, val)\n        index: int = self.hash_func(key)\n        self.buckets[index] = pair\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        index: int = self.hash_func(key)\n        # Set to None to represent removal\n        self.buckets[index] = None\n\n    def entry_set(self) -> list[Pair]:\n        \"\"\"Get all key-value pairs\"\"\"\n        result: list[Pair] = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair)\n        return result\n\n    def key_set(self) -> list[int]:\n        \"\"\"Get all keys\"\"\"\n        result = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair.key)\n        return result\n\n    def value_set(self) -> list[str]:\n        \"\"\"Get all values\"\"\"\n        result = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair.val)\n        return result\n\n    def print(self):\n        \"\"\"Print hash table\"\"\"\n        for pair in self.buckets:\n            if pair is not None:\n                print(pair.key, \"->\", pair.val)\n
array_hash_map.cpp
/* Key-value pair */\nstruct Pair {\n  public:\n    int key;\n    string val;\n    Pair(int key, string val) {\n        this->key = key;\n        this->val = val;\n    }\n};\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n  private:\n    vector<Pair *> buckets;\n\n  public:\n    ArrayHashMap() {\n        // Initialize array with 100 buckets\n        buckets = vector<Pair *>(100);\n    }\n\n    ~ArrayHashMap() {\n        // Free memory\n        for (const auto &bucket : buckets) {\n            delete bucket;\n        }\n        buckets.clear();\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Query operation */\n    string get(int key) {\n        int index = hashFunc(key);\n        Pair *pair = buckets[index];\n        if (pair == nullptr)\n            return \"\";\n        return pair->val;\n    }\n\n    /* Add operation */\n    void put(int key, string val) {\n        Pair *pair = new Pair(key, val);\n        int index = hashFunc(key);\n        buckets[index] = pair;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        // Free memory and set to nullptr\n        delete buckets[index];\n        buckets[index] = nullptr;\n    }\n\n    /* Get all key-value pairs */\n    vector<Pair *> pairSet() {\n        vector<Pair *> pairSet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                pairSet.push_back(pair);\n            }\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    vector<int> keySet() {\n        vector<int> keySet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                keySet.push_back(pair->key);\n            }\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    vector<string> valueSet() {\n        vector<string> valueSet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                valueSet.push_back(pair->val);\n            }\n        }\n        return valueSet;\n    }\n\n    /* Print hash table */\n    void print() {\n        for (Pair *kv : pairSet()) {\n            cout << kv->key << \" -> \" << kv->val << endl;\n        }\n    }\n};\n
array_hash_map.java
/* Key-value pair */\nclass Pair {\n    public int key;\n    public String val;\n\n    public Pair(int key, String val) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    private List<Pair> buckets;\n\n    public ArrayHashMap() {\n        // Initialize array with 100 buckets\n        buckets = new ArrayList<>();\n        for (int i = 0; i < 100; i++) {\n            buckets.add(null);\n        }\n    }\n\n    /* Hash function */\n    private int hashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Query operation */\n    public String get(int key) {\n        int index = hashFunc(key);\n        Pair pair = buckets.get(index);\n        if (pair == null)\n            return null;\n        return pair.val;\n    }\n\n    /* Add operation */\n    public void put(int key, String val) {\n        Pair pair = new Pair(key, val);\n        int index = hashFunc(key);\n        buckets.set(index, pair);\n    }\n\n    /* Remove operation */\n    public void remove(int key) {\n        int index = hashFunc(key);\n        // Set to null to represent deletion\n        buckets.set(index, null);\n    }\n\n    /* Get all key-value pairs */\n    public List<Pair> pairSet() {\n        List<Pair> pairSet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                pairSet.add(pair);\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    public List<Integer> keySet() {\n        List<Integer> keySet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                keySet.add(pair.key);\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    public List<String> valueSet() {\n        List<String> valueSet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                valueSet.add(pair.val);\n        }\n        return valueSet;\n    }\n\n    /* Print hash table */\n    public void print() {\n        for (Pair kv : pairSet()) {\n            System.out.println(kv.key + \" -> \" + kv.val);\n        }\n    }\n}\n
array_hash_map.cs
/* Key-value pair int->string */\nclass Pair(int key, string val) {\n    public int key = key;\n    public string val = val;\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    List<Pair?> buckets;\n    public ArrayHashMap() {\n        // Initialize array with 100 buckets\n        buckets = [];\n        for (int i = 0; i < 100; i++) {\n            buckets.Add(null);\n        }\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Query operation */\n    public string? Get(int key) {\n        int index = HashFunc(key);\n        Pair? pair = buckets[index];\n        if (pair == null) return null;\n        return pair.val;\n    }\n\n    /* Add operation */\n    public void Put(int key, string val) {\n        Pair pair = new(key, val);\n        int index = HashFunc(key);\n        buckets[index] = pair;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        int index = HashFunc(key);\n        // Set to null to represent deletion\n        buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    public List<Pair> PairSet() {\n        List<Pair> pairSet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                pairSet.Add(pair);\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    public List<int> KeySet() {\n        List<int> keySet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                keySet.Add(pair.key);\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    public List<string> ValueSet() {\n        List<string> valueSet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                valueSet.Add(pair.val);\n        }\n        return valueSet;\n    }\n\n    /* Print hash table */\n    public void Print() {\n        foreach (Pair kv in PairSet()) {\n            Console.WriteLine(kv.key + \" -> \" + kv.val);\n        }\n    }\n}\n
array_hash_map.go
/* Key-value pair */\ntype pair struct {\n    key int\n    val string\n}\n\n/* Hash table based on array implementation */\ntype arrayHashMap struct {\n    buckets []*pair\n}\n\n/* Initialize hash table */\nfunc newArrayHashMap() *arrayHashMap {\n    // Initialize array with 100 buckets\n    buckets := make([]*pair, 100)\n    return &arrayHashMap{buckets: buckets}\n}\n\n/* Hash function */\nfunc (a *arrayHashMap) hashFunc(key int) int {\n    index := key % 100\n    return index\n}\n\n/* Query operation */\nfunc (a *arrayHashMap) get(key int) string {\n    index := a.hashFunc(key)\n    pair := a.buckets[index]\n    if pair == nil {\n        return \"Not Found\"\n    }\n    return pair.val\n}\n\n/* Add operation */\nfunc (a *arrayHashMap) put(key int, val string) {\n    pair := &pair{key: key, val: val}\n    index := a.hashFunc(key)\n    a.buckets[index] = pair\n}\n\n/* Remove operation */\nfunc (a *arrayHashMap) remove(key int) {\n    index := a.hashFunc(key)\n    // Set to nil to delete\n    a.buckets[index] = nil\n}\n\n/* Get all key pairs */\nfunc (a *arrayHashMap) pairSet() []*pair {\n    var pairs []*pair\n    for _, pair := range a.buckets {\n        if pair != nil {\n            pairs = append(pairs, pair)\n        }\n    }\n    return pairs\n}\n\n/* Get all keys */\nfunc (a *arrayHashMap) keySet() []int {\n    var keys []int\n    for _, pair := range a.buckets {\n        if pair != nil {\n            keys = append(keys, pair.key)\n        }\n    }\n    return keys\n}\n\n/* Get all values */\nfunc (a *arrayHashMap) valueSet() []string {\n    var values []string\n    for _, pair := range a.buckets {\n        if pair != nil {\n            values = append(values, pair.val)\n        }\n    }\n    return values\n}\n\n/* Print hash table */\nfunc (a *arrayHashMap) print() {\n    for _, pair := range a.buckets {\n        if pair != nil {\n            fmt.Println(pair.key, \"->\", pair.val)\n        }\n    }\n}\n
array_hash_map.swift
/* Key-value pair */\nclass Pair: Equatable {\n    public var key: Int\n    public var val: String\n\n    public init(key: Int, val: String) {\n        self.key = key\n        self.val = val\n    }\n\n    public static func == (lhs: Pair, rhs: Pair) -> Bool {\n        lhs.key == rhs.key && lhs.val == rhs.val\n    }\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    private var buckets: [Pair?]\n\n    init() {\n        // Initialize array with 100 buckets\n        buckets = Array(repeating: nil, count: 100)\n    }\n\n    /* Hash function */\n    private func hashFunc(key: Int) -> Int {\n        let index = key % 100\n        return index\n    }\n\n    /* Query operation */\n    func get(key: Int) -> String? {\n        let index = hashFunc(key: key)\n        let pair = buckets[index]\n        return pair?.val\n    }\n\n    /* Add operation */\n    func put(key: Int, val: String) {\n        let pair = Pair(key: key, val: val)\n        let index = hashFunc(key: key)\n        buckets[index] = pair\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        let index = hashFunc(key: key)\n        // Set to nil to delete\n        buckets[index] = nil\n    }\n\n    /* Get all key-value pairs */\n    func pairSet() -> [Pair] {\n        buckets.compactMap { $0 }\n    }\n\n    /* Get all keys */\n    func keySet() -> [Int] {\n        buckets.compactMap { $0?.key }\n    }\n\n    /* Get all values */\n    func valueSet() -> [String] {\n        buckets.compactMap { $0?.val }\n    }\n\n    /* Print hash table */\n    func print() {\n        for pair in pairSet() {\n            Swift.print(\"\\(pair.key) -> \\(pair.val)\")\n        }\n    }\n}\n
array_hash_map.js
/* Key-value pair Number -> String */\nclass Pair {\n    constructor(key, val) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    #buckets;\n    constructor() {\n        // Initialize array with 100 buckets\n        this.#buckets = new Array(100).fill(null);\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % 100;\n    }\n\n    /* Query operation */\n    get(key) {\n        let index = this.#hashFunc(key);\n        let pair = this.#buckets[index];\n        if (pair === null) return null;\n        return pair.val;\n    }\n\n    /* Add operation */\n    set(key, val) {\n        let index = this.#hashFunc(key);\n        this.#buckets[index] = new Pair(key, val);\n    }\n\n    /* Remove operation */\n    delete(key) {\n        let index = this.#hashFunc(key);\n        // Set to null to represent deletion\n        this.#buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    entries() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i]);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all keys */\n    keys() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i].key);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all values */\n    values() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i].val);\n            }\n        }\n        return arr;\n    }\n\n    /* Print hash table */\n    print() {\n        let pairSet = this.entries();\n        for (const pair of pairSet) {\n            console.info(`${pair.key} -> ${pair.val}`);\n        }\n    }\n}\n
array_hash_map.ts
/* Key-value pair Number -> String */\nclass Pair {\n    public key: number;\n    public val: string;\n\n    constructor(key: number, val: string) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    private readonly buckets: (Pair | null)[];\n\n    constructor() {\n        // Initialize array with 100 buckets\n        this.buckets = new Array(100).fill(null);\n    }\n\n    /* Hash function */\n    private hashFunc(key: number): number {\n        return key % 100;\n    }\n\n    /* Query operation */\n    public get(key: number): string | null {\n        let index = this.hashFunc(key);\n        let pair = this.buckets[index];\n        if (pair === null) return null;\n        return pair.val;\n    }\n\n    /* Add operation */\n    public set(key: number, val: string) {\n        let index = this.hashFunc(key);\n        this.buckets[index] = new Pair(key, val);\n    }\n\n    /* Remove operation */\n    public delete(key: number) {\n        let index = this.hashFunc(key);\n        // Set to null to represent deletion\n        this.buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    public entries(): (Pair | null)[] {\n        let arr: (Pair | null)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i]);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all keys */\n    public keys(): (number | undefined)[] {\n        let arr: (number | undefined)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i].key);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all values */\n    public values(): (string | undefined)[] {\n        let arr: (string | undefined)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i].val);\n            }\n        }\n        return arr;\n    }\n\n    /* Print hash table */\n    public print() {\n        let pairSet = this.entries();\n        for (const pair of pairSet) {\n            console.info(`${pair.key} -> ${pair.val}`);\n        }\n    }\n}\n
array_hash_map.dart
/* Key-value pair */\nclass Pair {\n  int key;\n  String val;\n  Pair(this.key, this.val);\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n  late List<Pair?> _buckets;\n\n  ArrayHashMap() {\n    // Initialize array with 100 buckets\n    _buckets = List.filled(100, null);\n  }\n\n  /* Hash function */\n  int _hashFunc(int key) {\n    final int index = key % 100;\n    return index;\n  }\n\n  /* Query operation */\n  String? get(int key) {\n    final int index = _hashFunc(key);\n    final Pair? pair = _buckets[index];\n    if (pair == null) {\n      return null;\n    }\n    return pair.val;\n  }\n\n  /* Add operation */\n  void put(int key, String val) {\n    final Pair pair = Pair(key, val);\n    final int index = _hashFunc(key);\n    _buckets[index] = pair;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    final int index = _hashFunc(key);\n    _buckets[index] = null;\n  }\n\n  /* Get all key-value pairs */\n  List<Pair> pairSet() {\n    List<Pair> pairSet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        pairSet.add(pair);\n      }\n    }\n    return pairSet;\n  }\n\n  /* Get all keys */\n  List<int> keySet() {\n    List<int> keySet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        keySet.add(pair.key);\n      }\n    }\n    return keySet;\n  }\n\n  /* Get all values */\n  List<String> values() {\n    List<String> valueSet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        valueSet.add(pair.val);\n      }\n    }\n    return valueSet;\n  }\n\n  /* Print hash table */\n  void printHashMap() {\n    for (final Pair kv in pairSet()) {\n      print(\"${kv.key} -> ${kv.val}\");\n    }\n  }\n}\n
array_hash_map.rs
/* Key-value pair */\n#[derive(Debug, Clone, PartialEq)]\npub struct Pair {\n    pub key: i32,\n    pub val: String,\n}\n\n/* Hash table based on array implementation */\npub struct ArrayHashMap {\n    buckets: Vec<Option<Pair>>,\n}\n\nimpl ArrayHashMap {\n    pub fn new() -> ArrayHashMap {\n        // Initialize array with 100 buckets\n        Self {\n            buckets: vec![None; 100],\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        key as usize % 100\n    }\n\n    /* Query operation */\n    pub fn get(&self, key: i32) -> Option<&String> {\n        let index = self.hash_func(key);\n        self.buckets[index].as_ref().map(|pair| &pair.val)\n    }\n\n    /* Add operation */\n    pub fn put(&mut self, key: i32, val: &str) {\n        let index = self.hash_func(key);\n        self.buckets[index] = Some(Pair {\n            key,\n            val: val.to_string(),\n        });\n    }\n\n    /* Remove operation */\n    pub fn remove(&mut self, key: i32) {\n        let index = self.hash_func(key);\n        // Set to None to represent removal\n        self.buckets[index] = None;\n    }\n\n    /* Get all key-value pairs */\n    pub fn entry_set(&self) -> Vec<&Pair> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref())\n            .collect()\n    }\n\n    /* Get all keys */\n    pub fn key_set(&self) -> Vec<&i32> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref().map(|pair| &pair.key))\n            .collect()\n    }\n\n    /* Get all values */\n    pub fn value_set(&self) -> Vec<&String> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref().map(|pair| &pair.val))\n            .collect()\n    }\n\n    /* Print hash table */\n    pub fn print(&self) {\n        for pair in self.entry_set() {\n            println!(\"{} -> {}\", pair.key, pair.val);\n        }\n    }\n}\n
array_hash_map.c
/* Key-value pair int->string */\ntypedef struct {\n    int key;\n    char *val;\n} Pair;\n\n/* Hash table based on array implementation */\ntypedef struct {\n    Pair *buckets[MAX_SIZE];\n} ArrayHashMap;\n\n/* Constructor */\nArrayHashMap *newArrayHashMap() {\n    ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap));\n    for (int i=0; i < MAX_SIZE; i++) {\n        hmap->buckets[i] = NULL;\n    }\n    return hmap;\n}\n\n/* Destructor */\nvoid delArrayHashMap(ArrayHashMap *hmap) {\n    for (int i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            free(hmap->buckets[i]->val);\n            free(hmap->buckets[i]);\n        }\n    }\n    free(hmap);\n}\n\n/* Add operation */\nvoid put(ArrayHashMap *hmap, const int key, const char *val) {\n    Pair *Pair = malloc(sizeof(Pair));\n    Pair->key = key;\n    Pair->val = malloc(strlen(val) + 1);\n    strcpy(Pair->val, val);\n\n    int index = hashFunc(key);\n    hmap->buckets[index] = Pair;\n}\n\n/* Remove operation */\nvoid removeItem(ArrayHashMap *hmap, const int key) {\n    int index = hashFunc(key);\n    free(hmap->buckets[index]->val);\n    free(hmap->buckets[index]);\n    hmap->buckets[index] = NULL;\n}\n\n/* Get all key-value pairs */\nvoid pairSet(ArrayHashMap *hmap, MapSet *set) {\n    Pair *entries;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count valid key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    entries = malloc(sizeof(Pair) * total);\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            entries[index].key = hmap->buckets[i]->key;\n            entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1);\n            strcpy(entries[index].val, hmap->buckets[i]->val);\n            index++;\n        }\n    }\n    set->set = entries;\n    set->len = total;\n}\n\n/* Get all keys */\nvoid keySet(ArrayHashMap *hmap, MapSet *set) {\n    int *keys;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count valid key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    keys = malloc(total * sizeof(int));\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            keys[index] = hmap->buckets[i]->key;\n            index++;\n        }\n    }\n    set->set = keys;\n    set->len = total;\n}\n\n/* Get all values */\nvoid valueSet(ArrayHashMap *hmap, MapSet *set) {\n    char **vals;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count valid key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    vals = malloc(total * sizeof(char *));\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            vals[index] = hmap->buckets[i]->val;\n            index++;\n        }\n    }\n    set->set = vals;\n    set->len = total;\n}\n\n/* Print hash table */\nvoid print(ArrayHashMap *hmap) {\n    int i;\n    MapSet set;\n    pairSet(hmap, &set);\n    Pair *entries = (Pair *)set.set;\n    for (i = 0; i < set.len; i++) {\n        printf(\"%d -> %s\\n\", entries[i].key, entries[i].val);\n    }\n    free(set.set);\n}\n
array_hash_map.kt
/* Key-value pair */\nclass Pair(\n    var key: Int,\n    var _val: String\n)\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    // Initialize array with 100 buckets\n    private val buckets = arrayOfNulls<Pair>(100)\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        val index = key % 100\n        return index\n    }\n\n    /* Query operation */\n    fun get(key: Int): String? {\n        val index = hashFunc(key)\n        val pair = buckets[index] ?: return null\n        return pair._val\n    }\n\n    /* Add operation */\n    fun put(key: Int, _val: String) {\n        val pair = Pair(key, _val)\n        val index = hashFunc(key)\n        buckets[index] = pair\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        val index = hashFunc(key)\n        // Set to null to represent deletion\n        buckets[index] = null\n    }\n\n    /* Get all key-value pairs */\n    fun pairSet(): MutableList<Pair> {\n        val pairSet = mutableListOf<Pair>()\n        for (pair in buckets) {\n            if (pair != null)\n                pairSet.add(pair)\n        }\n        return pairSet\n    }\n\n    /* Get all keys */\n    fun keySet(): MutableList<Int> {\n        val keySet = mutableListOf<Int>()\n        for (pair in buckets) {\n            if (pair != null)\n                keySet.add(pair.key)\n        }\n        return keySet\n    }\n\n    /* Get all values */\n    fun valueSet(): MutableList<String> {\n        val valueSet = mutableListOf<String>()\n        for (pair in buckets) {\n            if (pair != null)\n                valueSet.add(pair._val)\n        }\n        return valueSet\n    }\n\n    /* Print hash table */\n    fun print() {\n        for (kv in pairSet()) {\n            val key = kv.key\n            val _val = kv._val\n            println(\"$key -> $_val\")\n        }\n    }\n}\n
array_hash_map.rb
### Key-value pair ###\nclass Pair\n  attr_accessor :key, :val\n\n  def initialize(key, val)\n    @key = key\n    @val = val\n  end\nend\n\n### Hash map based on array ###\nclass ArrayHashMap\n  ### Constructor ###\n  def initialize\n    # Initialize array with 100 buckets\n    @buckets = Array.new(100)\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    index = key % 100\n  end\n\n  ### Query operation ###\n  def get(key)\n    index = hash_func(key)\n    pair = @buckets[index]\n\n    return if pair.nil?\n    pair.val\n  end\n\n  ### Add operation ###\n  def put(key, val)\n    pair = Pair.new(key, val)\n    index = hash_func(key)\n    @buckets[index] = pair\n  end\n\n  ### Delete operation ###\n  def remove(key)\n    index = hash_func(key)\n    # Set to nil to delete\n    @buckets[index] = nil\n  end\n\n  ### Get all key-value pairs ###\n  def entry_set\n    result = []\n    @buckets.each { |pair| result << pair unless pair.nil? }\n    result\n  end\n\n  ### Get all keys ###\n  def key_set\n    result = []\n    @buckets.each { |pair| result << pair.key unless pair.nil? }\n    result\n  end\n\n  ### Get all values ###\n  def value_set\n    result = []\n    @buckets.each { |pair| result << pair.val unless pair.nil? }\n    result\n  end\n\n  ### Print hash table ###\n  def print\n    @buckets.each { |pair| puts \"#{pair.key} -> #{pair.val}\" unless pair.nil? }\n  end\nend\n
","path":["Chapter 6. Hashing","6.1   Hash Table"],"tags":[]},{"location":"chapter_hashing/hash_map/#613-hash-collision-and-resizing","level":2,"title":"6.1.3   Hash Collision and Resizing","text":"

Fundamentally, the role of a hash function is to map the input space consisting of all keys to the output space consisting of all array indices, and the input space is often much larger than the output space. Therefore, theoretically there must be cases where \"multiple inputs correspond to the same output\".

For the hash function in the above example, when the input keys have the same last two digits, the hash function produces the same output. For example, when querying two students with IDs 12836 and 20336, we get:

12836 % 100 = 36\n20336 % 100 = 36\n

As shown in Figure 6-3, two student IDs point to the same name, which is obviously incorrect. We call this situation where multiple inputs correspond to the same output a hash collision.

Figure 6-3   Hash collision example

It's easy to see that the larger the hash table capacity \\(n\\), the lower the probability that multiple keys will be assigned to the same bucket, and the fewer collisions. Therefore, we can reduce hash collisions by expanding the hash table.

As shown in Figure 6-4, before expansion, the key-value pairs (136, A) and (236, D) collided, but after expansion, the collision disappears.

Figure 6-4   Hash table resizing

Similar to array expansion, hash table expansion requires migrating all key-value pairs from the original hash table to the new hash table, which is very time-consuming. Moreover, since the hash table capacity capacity changes, we need to recalculate the storage locations of all key-value pairs through the hash function, further increasing the computational overhead of the expansion process. For this reason, programming languages typically reserve a sufficiently large hash table capacity to prevent frequent expansion.

The load factor is an important concept for hash tables. It is defined as the number of elements in the hash table divided by the number of buckets, and is used to measure the severity of hash collisions. It is also commonly used as a trigger condition for hash table expansion. For example, in Java, when the load factor exceeds \\(0.75\\), the system will expand the hash table to \\(2\\) times its original size.

","path":["Chapter 6. Hashing","6.1   Hash Table"],"tags":[]},{"location":"chapter_hashing/summary/","level":1,"title":"6.4   Summary","text":"","path":["Chapter 6. Hashing","6.4   Summary"],"tags":[]},{"location":"chapter_hashing/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Given an input key, a hash table can retrieve the corresponding value in \\(O(1)\\) time, which is highly efficient.
  • Common hash table operations include querying, adding key-value pairs, deleting key-value pairs, and traversing the hash table.
  • The hash function maps a key to an array index, allowing access to the corresponding bucket and retrieval of the value.
  • Two different keys may end up with the same array index after hashing, leading to erroneous query results. This phenomenon is known as hash collision.
  • The larger the capacity of the hash table, the lower the probability of hash collisions. Therefore, hash table expansion can mitigate hash collisions. Similar to array expansion, hash table expansion is costly.
  • The load factor, defined as the number of elements divided by the number of buckets, reflects the severity of hash collisions and is often used as a condition to trigger hash table expansion.
  • Separate chaining addresses hash collisions by converting each element into a linked list, storing all colliding elements in the same linked list. However, excessively long linked lists can reduce query efficiency, which can be improved by converting the linked lists into red-black trees.
  • Open addressing handles hash collisions through multiple probing. Linear probing uses a fixed step size but cannot delete elements and is prone to clustering. Double hashing uses multiple hash functions for probing, which reduces clustering compared to linear probing but increases computational overhead.
  • Different programming languages adopt various hash table implementations. For example, Java's HashMap uses separate chaining, while Python's dict employs open addressing.
  • In hash tables, we desire hash algorithms with determinism, high efficiency, and uniform distribution. In cryptography, hash algorithms should also possess collision resistance and the avalanche effect.
  • Hash algorithms typically use large prime numbers as moduli to maximize the uniform distribution of hash values and reduce hash collisions.
  • Common hash algorithms include MD5, SHA-1, SHA-2, and SHA-3. MD5 is often used for file integrity checks, while SHA-2 is commonly used in secure applications and protocols.
  • Programming languages usually provide built-in hash algorithms for data types to calculate bucket indices in hash tables. Generally, only immutable objects are hashable.
","path":["Chapter 6. Hashing","6.4   Summary"],"tags":[]},{"location":"chapter_hashing/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: When does the time complexity of a hash table degrade to \\(O(n)\\)?

The time complexity of a hash table can degrade to \\(O(n)\\) when hash collisions are severe. When the hash function is well-designed, the capacity is set appropriately, and collisions are evenly distributed, the time complexity is \\(O(1)\\). We usually consider the time complexity to be \\(O(1)\\) when using built-in hash tables in programming languages.

Q: Why not use the hash function \\(f(x) = x\\)? This would eliminate collisions.

Under the hash function \\(f(x) = x\\), each element corresponds to a unique bucket index, which is equivalent to an array. However, the input space is usually much larger than the output space (array length), so the last step of a hash function is often to take the modulo of the array length. In other words, the goal of a hash table is to map a larger state space to a smaller one while providing \\(O(1)\\) query efficiency.

Q: Why can hash tables be more efficient than arrays, linked lists, or binary trees, even though hash tables are implemented using these structures?

Firstly, hash tables have higher time efficiency but lower space efficiency. A significant portion of memory in hash tables remains unused.

Secondly, hash tables are only more time-efficient in specific use cases. If a feature can be implemented with the same time complexity using an array or a linked list, it's usually faster than using a hash table. This is because the computation of the hash function incurs overhead, making the constant factor in the time complexity larger.

Lastly, the time complexity of hash tables can degrade. For example, in separate chaining, we perform search operations in a linked list or red-black tree, which still risks degrading to \\(O(n)\\) time.

Q: Does double hashing also have the flaw of not being able to delete elements directly? Can space marked as deleted be reused?

Double hashing is a form of open addressing, and all open addressing methods have the drawback of not being able to delete elements directly; they require marking elements as deleted. Marked spaces can be reused. When inserting new elements into the hash table, and the hash function points to a position marked as deleted, that position can be used by the new element. This maintains the probing sequence of the hash table while ensuring efficient use of space.

Q: Why do hash collisions occur during the search process in linear probing?

During the search process, the hash function points to the corresponding bucket and key-value pair. If the key doesn't match, it indicates a hash collision. Therefore, linear probing will search downward at a predetermined step size until the correct key-value pair is found or the search fails.

Q: Why can expanding a hash table alleviate hash collisions?

The last step of a hash function often involves taking the modulo of the array length \\(n\\), to keep the output within the array index range. When expanding, the array length \\(n\\) changes, and the indices corresponding to the keys may also change. Keys that were previously mapped to the same bucket might be distributed across multiple buckets after expansion, thereby mitigating hash collisions.

","path":["Chapter 6. Hashing","6.4   Summary"],"tags":[]},{"location":"chapter_heap/","level":1,"title":"Chapter 8.   Heap","text":"

Abstract

Heaps are like mountain peaks, layered and undulating, each with its unique form.

The peaks rise and fall at varying heights, yet the tallest peak always catches the eye first.

","path":["Chapter 8. Heap","Chapter 8.   Heap"],"tags":[]},{"location":"chapter_heap/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 8.1   Heap
  • 8.2   Building a Heap
  • 8.3   Top-K Problem
  • 8.4   Summary
","path":["Chapter 8. Heap","Chapter 8.   Heap"],"tags":[]},{"location":"chapter_heap/build_heap/","level":1,"title":"8.2   Heap Construction Operation","text":"

In some cases, we want to build a heap using all elements of a list, and this process is called \"heap construction operation.\"

","path":["Chapter 8. Heap","8.2   Heap Construction Operation"],"tags":[]},{"location":"chapter_heap/build_heap/#821-implementing-with-element-insertion","level":2,"title":"8.2.1   Implementing with Element Insertion","text":"

We first create an empty heap, then iterate through the list, performing the \"element insertion operation\" on each element in sequence. This means adding the element to the bottom of the heap and then performing \"bottom-to-top\" heapify on that element.

Each time an element is inserted into the heap, the heap's length increases by one. Since nodes are added to the binary tree sequentially from top to bottom, the heap is constructed \"from top to bottom.\"

Given \\(n\\) elements, each element's insertion operation takes \\(O(\\log{n})\\) time, so the time complexity of this heap construction method is \\(O(n \\log n)\\).

","path":["Chapter 8. Heap","8.2   Heap Construction Operation"],"tags":[]},{"location":"chapter_heap/build_heap/#822-implementing-through-heapify-traversal","level":2,"title":"8.2.2   Implementing Through Heapify Traversal","text":"

In fact, we can implement a more efficient heap construction method in two steps.

  1. Add all elements of the list as-is to the heap, at which point the heap property is not yet satisfied.
  2. Traverse the heap in reverse order (reverse of level-order traversal), performing \"top-to-bottom heapify\" on each non-leaf node in sequence.

After heapifying a node, the subtree rooted at that node becomes a valid sub-heap. Since we traverse in reverse order, the heap is constructed \"from bottom to top.\"

The reason for choosing reverse order traversal is that it ensures the subtree below the current node is already a valid sub-heap, making the heapification of the current node effective.

It's worth noting that since leaf nodes have no children, they are naturally valid sub-heaps and do not require heapification. As shown in the code below, the last non-leaf node is the parent of the last node; we start from it and traverse in reverse order to perform heapification:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
def __init__(self, nums: list[int]):\n    \"\"\"Constructor, build heap based on input list\"\"\"\n    # Add list elements to heap as is\n    self.max_heap = nums\n    # Heapify all nodes except leaf nodes\n    for i in range(self.parent(self.size() - 1), -1, -1):\n        self.sift_down(i)\n
my_heap.cpp
/* Constructor, build heap based on input list */\nMaxHeap(vector<int> nums) {\n    // Add list elements to heap as is\n    maxHeap = nums;\n    // Heapify all nodes except leaf nodes\n    for (int i = parent(size() - 1); i >= 0; i--) {\n        siftDown(i);\n    }\n}\n
my_heap.java
/* Constructor, build heap based on input list */\nMaxHeap(List<Integer> nums) {\n    // Add list elements to heap as is\n    maxHeap = new ArrayList<>(nums);\n    // Heapify all nodes except leaf nodes\n    for (int i = parent(size() - 1); i >= 0; i--) {\n        siftDown(i);\n    }\n}\n
my_heap.cs
/* Constructor, build heap from input list */\nMaxHeap(IEnumerable<int> nums) {\n    // Add list elements to heap as is\n    maxHeap = new List<int>(nums);\n    // Heapify all nodes except leaf nodes\n    var size = Parent(this.Size() - 1);\n    for (int i = size; i >= 0; i--) {\n        SiftDown(i);\n    }\n}\n
my_heap.go
/* Constructor, build heap from slice */\nfunc newMaxHeap(nums []any) *maxHeap {\n    // Add list elements to heap as is\n    h := &maxHeap{data: nums}\n    for i := h.parent(len(h.data) - 1); i >= 0; i-- {\n        // Heapify all nodes except leaf nodes\n        h.siftDown(i)\n    }\n    return h\n}\n
my_heap.swift
/* Constructor, build heap based on input list */\ninit(nums: [Int]) {\n    // Add list elements to heap as is\n    maxHeap = nums\n    // Heapify all nodes except leaf nodes\n    for i in (0 ... parent(i: size() - 1)).reversed() {\n        siftDown(i: i)\n    }\n}\n
my_heap.js
/* Constructor, build empty heap or build heap from input list */\nconstructor(nums) {\n    // Add list elements to heap as is\n    this.#maxHeap = nums === undefined ? [] : [...nums];\n    // Heapify all nodes except leaf nodes\n    for (let i = this.#parent(this.size() - 1); i >= 0; i--) {\n        this.#siftDown(i);\n    }\n}\n
my_heap.ts
/* Constructor, build empty heap or build heap from input list */\nconstructor(nums?: number[]) {\n    // Add list elements to heap as is\n    this.maxHeap = nums === undefined ? [] : [...nums];\n    // Heapify all nodes except leaf nodes\n    for (let i = this.parent(this.size() - 1); i >= 0; i--) {\n        this.siftDown(i);\n    }\n}\n
my_heap.dart
/* Constructor, build heap based on input list */\nMaxHeap(List<int> nums) {\n  // Add list elements to heap as is\n  _maxHeap = nums;\n  // Heapify all nodes except leaf nodes\n  for (int i = _parent(size() - 1); i >= 0; i--) {\n    siftDown(i);\n  }\n}\n
my_heap.rs
/* Constructor, build heap based on input list */\nfn new(nums: Vec<i32>) -> Self {\n    // Add list elements to heap as is\n    let mut heap = MaxHeap { max_heap: nums };\n    // Heapify all nodes except leaf nodes\n    for i in (0..=Self::parent(heap.size() - 1)).rev() {\n        heap.sift_down(i);\n    }\n    heap\n}\n
my_heap.c
/* Constructor, build heap from slice */\nMaxHeap *newMaxHeap(int nums[], int size) {\n    // Push all elements to heap\n    MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap));\n    maxHeap->size = size;\n    memcpy(maxHeap->data, nums, size * sizeof(int));\n    for (int i = parent(maxHeap, size - 1); i >= 0; i--) {\n        // Heapify all nodes except leaf nodes\n        siftDown(maxHeap, i);\n    }\n    return maxHeap;\n}\n
my_heap.kt
/* Max heap */\nclass MaxHeap(nums: MutableList<Int>?) {\n    // Use list instead of array, no need to consider capacity expansion\n    private val maxHeap = mutableListOf<Int>()\n\n    /* Constructor, build heap based on input list */\n    init {\n        // Add list elements to heap as is\n        maxHeap.addAll(nums!!)\n        // Heapify all nodes except leaf nodes\n        for (i in parent(size() - 1) downTo 0) {\n            siftDown(i)\n        }\n    }\n\n    /* Get index of left child node */\n    private fun left(i: Int): Int {\n        return 2 * i + 1\n    }\n\n    /* Get index of right child node */\n    private fun right(i: Int): Int {\n        return 2 * i + 2\n    }\n\n    /* Get index of parent node */\n    private fun parent(i: Int): Int {\n        return (i - 1) / 2 // Floor division\n    }\n\n    /* Swap elements */\n    private fun swap(i: Int, j: Int) {\n        val temp = maxHeap[i]\n        maxHeap[i] = maxHeap[j]\n        maxHeap[j] = temp\n    }\n\n    /* Get heap size */\n    fun size(): Int {\n        return maxHeap.size\n    }\n\n    /* Check if heap is empty */\n    fun isEmpty(): Boolean {\n        /* Check if heap is empty */\n        return size() == 0\n    }\n\n    /* Access top element */\n    fun peek(): Int {\n        return maxHeap[0]\n    }\n\n    /* Element enters heap */\n    fun push(_val: Int) {\n        // Add node\n        maxHeap.add(_val)\n        // Heapify from bottom to top\n        siftUp(size() - 1)\n    }\n\n    /* Starting from node i, heapify from bottom to top */\n    private fun siftUp(it: Int) {\n        // Kotlin function parameters are immutable, so create temporary variable\n        var i = it\n        while (true) {\n            // Get parent node of node i\n            val p = parent(i)\n            // When \"crossing root node\" or \"node needs no repair\", end heapify\n            if (p < 0 || maxHeap[i] <= maxHeap[p]) break\n            // Swap two nodes\n            swap(i, p)\n            // Loop upward heapify\n            i = p\n        }\n    }\n\n    /* Element exits heap */\n    fun pop(): Int {\n        // Handle empty case\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        // Delete node\n        swap(0, size() - 1)\n        // Remove node\n        val _val = maxHeap.removeAt(size() - 1)\n        // Return top element\n        siftDown(0)\n        // Return heap top element\n        return _val\n    }\n\n    /* Starting from node i, heapify from top to bottom */\n    private fun siftDown(it: Int) {\n        // Kotlin function parameters are immutable, so create temporary variable\n        var i = it\n        while (true) {\n            // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n            val l = left(i)\n            val r = right(i)\n            var ma = i\n            if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l\n            if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r\n            // Swap two nodes\n            if (ma == i) break\n            // Swap two nodes\n            swap(i, ma)\n            // Loop downwards heapification\n            i = ma\n        }\n    }\n\n    /* Driver Code */\n    fun print() {\n        val queue = PriorityQueue { a: Int, b: Int -> b - a }\n        queue.addAll(maxHeap)\n        printHeap(queue)\n    }\n}\n
my_heap.rb
### Constructor, build heap from input list ###\ndef initialize(nums)\n  # Add list elements to heap as is\n  @max_heap = nums\n  # Heapify all nodes except leaf nodes\n  parent(size - 1).downto(0) do |i|\n    sift_down(i)\n  end\nend\n
","path":["Chapter 8. Heap","8.2   Heap Construction Operation"],"tags":[]},{"location":"chapter_heap/build_heap/#823-complexity-analysis","level":2,"title":"8.2.3   Complexity Analysis","text":"

Next, let's attempt to derive the time complexity of this second heap construction method.

  • Assuming the complete binary tree has \\(n\\) nodes, then the number of leaf nodes is \\((n + 1) / 2\\), where \\(/\\) is floor division. Therefore, the number of nodes that need heapification is \\((n - 1) / 2\\).
  • In the top-to-bottom heapify process, each node is heapified at most to the leaf nodes, so the maximum number of iterations is the binary tree height \\(\\log n\\).

Multiplying these two together, we get a time complexity of \\(O(n \\log n)\\) for the heap construction process. However, this estimate is not accurate because it doesn't account for the property that binary trees have far more nodes at lower levels than at upper levels.

Let's perform a more accurate calculation. To reduce calculation difficulty, assume a \"perfect binary tree\" with \\(n\\) nodes and height \\(h\\); this assumption does not affect the correctness of the result.

Figure 8-5   Node count at each level of a perfect binary tree

As shown in Figure 8-5, the maximum number of iterations for a node's \"top-to-bottom heapify\" equals the distance from that node to the leaf nodes, which is precisely the \"node height.\" Therefore, we can sum the \"number of nodes \\(\\times\\) node height\" at each level to obtain the total number of heapify iterations for all nodes.

\\[ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \\dots + 2^{(h-1)}\\times1 \\]

To simplify the above expression, we need to use sequence knowledge from high school. First, multiply \\(T(h)\\) by \\(2\\) to get:

\\[ \\begin{aligned} T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \\dots + 2^{h-1}\\times1 \\newline 2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \\dots + 2^{h}\\times1 \\newline \\end{aligned} \\]

Using the method of differences, subtract the first equation \\(T(h)\\) from the second equation \\(2 T(h)\\) to get:

\\[ 2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \\dots + 2^{h-1} + 2^h \\]

Observing the above expression, we find that \\(T(h)\\) is a geometric series, which can be calculated directly using the sum formula, yielding a time complexity of:

\\[ \\begin{aligned} T(h) & = 2 \\frac{1 - 2^h}{1 - 2} - h \\newline & = 2^{h+1} - h - 2 \\newline & = O(2^h) \\end{aligned} \\]

Furthermore, a perfect binary tree with height \\(h\\) has \\(n = 2^{h+1} - 1\\) nodes, so the complexity is \\(O(2^h) = O(n)\\). This derivation shows that the time complexity of building a heap from an input list is \\(O(n)\\), which is highly efficient.

","path":["Chapter 8. Heap","8.2   Heap Construction Operation"],"tags":[]},{"location":"chapter_heap/heap/","level":1,"title":"8.1   Heap","text":"

A heap is a complete binary tree that satisfies specific conditions and can be mainly categorized into two types, as shown in Figure 8-1.

  • min heap: The value of any node \\(\\leq\\) the values of its child nodes.
  • max heap: The value of any node \\(\\geq\\) the values of its child nodes.

Figure 8-1   Min heap and max heap

As a special case of a complete binary tree, heaps have the following characteristics.

  • The bottom layer nodes are filled from left to right, and nodes in other layers are fully filled.
  • We call the root node of the binary tree the \"heap top\" and the bottom-rightmost node the \"heap bottom.\"
  • For max heaps (min heaps), the value of the heap top element (root node) is the largest (smallest).
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#811-common-heap-operations","level":2,"title":"8.1.1   Common Heap Operations","text":"

It should be noted that many programming languages provide a priority queue, which is an abstract data structure defined as a queue with priority sorting.

In fact, heaps are typically used to implement priority queues, with max heaps corresponding to priority queues where elements are dequeued in descending order. From a usage perspective, we can regard \"priority queue\" and \"heap\" as equivalent data structures. Therefore, this book does not make a special distinction between the two and uniformly refers to them as \"heap.\"

Common heap operations are shown in Table 8-1, and method names need to be determined based on the programming language.

Table 8-1   Efficiency of Heap Operations

Method name Description Time complexity push() Insert an element into the heap \\(O(\\log n)\\) pop() Remove the heap top element \\(O(\\log n)\\) peek() Access the heap top element (max/min value for max/min heap) \\(O(1)\\) size() Get the number of elements in the heap \\(O(1)\\) isEmpty() Check if the heap is empty \\(O(1)\\)

In practical applications, we can directly use the heap class (or priority queue class) provided by programming languages.

Similar to \"ascending order\" and \"descending order\" in sorting algorithms, we can implement conversion between \"min heap\" and \"max heap\" by setting a flag or modifying the Comparator. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby heap.py
# Initialize a min heap\nmin_heap, flag = [], 1\n# Initialize a max heap\nmax_heap, flag = [], -1\n\n# Python's heapq module implements a min heap by default\n# Consider negating elements before pushing them to the heap, which inverts the size relationship and thus implements a max heap\n# In this example, flag = 1 corresponds to a min heap, flag = -1 corresponds to a max heap\n\n# Push elements into the heap\nheapq.heappush(max_heap, flag * 1)\nheapq.heappush(max_heap, flag * 3)\nheapq.heappush(max_heap, flag * 2)\nheapq.heappush(max_heap, flag * 5)\nheapq.heappush(max_heap, flag * 4)\n\n# Get the heap top element\npeek: int = flag * max_heap[0] # 5\n\n# Remove the heap top element\n# The removed elements will form a descending sequence\nval = flag * heapq.heappop(max_heap) # 5\nval = flag * heapq.heappop(max_heap) # 4\nval = flag * heapq.heappop(max_heap) # 3\nval = flag * heapq.heappop(max_heap) # 2\nval = flag * heapq.heappop(max_heap) # 1\n\n# Get the heap size\nsize: int = len(max_heap)\n\n# Check if the heap is empty\nis_empty: bool = not max_heap\n\n# Build a heap from an input list\nmin_heap: list[int] = [1, 3, 2, 5, 4]\nheapq.heapify(min_heap)\n
heap.cpp
/* Initialize a heap */\n// Initialize a min heap\npriority_queue<int, vector<int>, greater<int>> minHeap;\n// Initialize a max heap\npriority_queue<int, vector<int>, less<int>> maxHeap;\n\n/* Push elements into the heap */\nmaxHeap.push(1);\nmaxHeap.push(3);\nmaxHeap.push(2);\nmaxHeap.push(5);\nmaxHeap.push(4);\n\n/* Get the heap top element */\nint peek = maxHeap.top(); // 5\n\n/* Remove the heap top element */\n// The removed elements will form a descending sequence\nmaxHeap.pop(); // 5\nmaxHeap.pop(); // 4\nmaxHeap.pop(); // 3\nmaxHeap.pop(); // 2\nmaxHeap.pop(); // 1\n\n/* Get the heap size */\nint size = maxHeap.size();\n\n/* Check if the heap is empty */\nbool isEmpty = maxHeap.empty();\n\n/* Build a heap from an input list */\nvector<int> input{1, 3, 2, 5, 4};\npriority_queue<int, vector<int>, greater<int>> minHeap(input.begin(), input.end());\n
heap.java
/* Initialize a heap */\n// Initialize a min heap\nQueue<Integer> minHeap = new PriorityQueue<>();\n// Initialize a max heap (use lambda expression to modify Comparator)\nQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);\n\n/* Push elements into the heap */\nmaxHeap.offer(1);\nmaxHeap.offer(3);\nmaxHeap.offer(2);\nmaxHeap.offer(5);\nmaxHeap.offer(4);\n\n/* Get the heap top element */\nint peek = maxHeap.peek(); // 5\n\n/* Remove the heap top element */\n// The removed elements will form a descending sequence\npeek = maxHeap.poll(); // 5\npeek = maxHeap.poll(); // 4\npeek = maxHeap.poll(); // 3\npeek = maxHeap.poll(); // 2\npeek = maxHeap.poll(); // 1\n\n/* Get the heap size */\nint size = maxHeap.size();\n\n/* Check if the heap is empty */\nboolean isEmpty = maxHeap.isEmpty();\n\n/* Build a heap from an input list */\nminHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4));\n
heap.cs
/* Initialize a heap */\n// Initialize a min heap\nPriorityQueue<int, int> minHeap = new();\n// Initialize a max heap (use lambda expression to modify Comparer)\nPriorityQueue<int, int> maxHeap = new(Comparer<int>.Create((x, y) => y.CompareTo(x)));\n\n/* Push elements into the heap */\nmaxHeap.Enqueue(1, 1);\nmaxHeap.Enqueue(3, 3);\nmaxHeap.Enqueue(2, 2);\nmaxHeap.Enqueue(5, 5);\nmaxHeap.Enqueue(4, 4);\n\n/* Get the heap top element */\nint peek = maxHeap.Peek();//5\n\n/* Remove the heap top element */\n// The removed elements will form a descending sequence\npeek = maxHeap.Dequeue();  // 5\npeek = maxHeap.Dequeue();  // 4\npeek = maxHeap.Dequeue();  // 3\npeek = maxHeap.Dequeue();  // 2\npeek = maxHeap.Dequeue();  // 1\n\n/* Get the heap size */\nint size = maxHeap.Count;\n\n/* Check if the heap is empty */\nbool isEmpty = maxHeap.Count == 0;\n\n/* Build a heap from an input list */\nminHeap = new PriorityQueue<int, int>([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]);\n
heap.go
// In Go, we can construct a max heap of integers by implementing heap.Interface\n// Implementing heap.Interface also requires implementing sort.Interface\ntype intHeap []any\n\n// Push implements the heap.Interface method for pushing an element into the heap\nfunc (h *intHeap) Push(x any) {\n    // Push and Pop use pointer receiver as parameters\n    // because they not only adjust the slice contents but also modify the slice length\n    *h = append(*h, x.(int))\n}\n\n// Pop implements the heap.Interface method for popping the heap top element\nfunc (h *intHeap) Pop() any {\n    // The element to be removed is stored at the end\n    last := (*h)[len(*h)-1]\n    *h = (*h)[:len(*h)-1]\n    return last\n}\n\n// Len is a sort.Interface method\nfunc (h *intHeap) Len() int {\n    return len(*h)\n}\n\n// Less is a sort.Interface method\nfunc (h *intHeap) Less(i, j int) bool {\n    // To implement a min heap, change this to a less-than sign\n    return (*h)[i].(int) > (*h)[j].(int)\n}\n\n// Swap is a sort.Interface method\nfunc (h *intHeap) Swap(i, j int) {\n    (*h)[i], (*h)[j] = (*h)[j], (*h)[i]\n}\n\n// Top gets the heap top element\nfunc (h *intHeap) Top() any {\n    return (*h)[0]\n}\n\n/* Driver Code */\nfunc TestHeap(t *testing.T) {\n    /* Initialize a heap */\n    // Initialize a max heap\n    maxHeap := &intHeap{}\n    heap.Init(maxHeap)\n    /* Push elements into the heap */\n    // Call heap.Interface methods to add elements\n    heap.Push(maxHeap, 1)\n    heap.Push(maxHeap, 3)\n    heap.Push(maxHeap, 2)\n    heap.Push(maxHeap, 4)\n    heap.Push(maxHeap, 5)\n\n    /* Get the heap top element */\n    top := maxHeap.Top()\n    fmt.Printf(\"Heap top element is %d\\n\", top)\n\n    /* Remove the heap top element */\n    // Call heap.Interface methods to remove elements\n    heap.Pop(maxHeap) // 5\n    heap.Pop(maxHeap) // 4\n    heap.Pop(maxHeap) // 3\n    heap.Pop(maxHeap) // 2\n    heap.Pop(maxHeap) // 1\n\n    /* Get the heap size */\n    size := len(*maxHeap)\n    fmt.Printf(\"Number of heap elements is %d\\n\", size)\n\n    /* Check if the heap is empty */\n    isEmpty := len(*maxHeap) == 0\n    fmt.Printf(\"Is the heap empty? %t\\n\", isEmpty)\n}\n
heap.swift
/* Initialize a heap */\n// Swift's Heap type supports both max heaps and min heaps, and requires importing swift-collections\nvar heap = Heap<Int>()\n\n/* Push elements into the heap */\nheap.insert(1)\nheap.insert(3)\nheap.insert(2)\nheap.insert(5)\nheap.insert(4)\n\n/* Get the heap top element */\nvar peek = heap.max()!\n\n/* Remove the heap top element */\npeek = heap.removeMax() // 5\npeek = heap.removeMax() // 4\npeek = heap.removeMax() // 3\npeek = heap.removeMax() // 2\npeek = heap.removeMax() // 1\n\n/* Get the heap size */\nlet size = heap.count\n\n/* Check if the heap is empty */\nlet isEmpty = heap.isEmpty\n\n/* Build a heap from an input list */\nlet heap2 = Heap([1, 3, 2, 5, 4])\n
heap.js
// JavaScript does not provide a built-in Heap class\n
heap.ts
// TypeScript does not provide a built-in Heap class\n
heap.dart
// Dart does not provide a built-in Heap class\n
heap.rs
use std::collections::BinaryHeap;\nuse std::cmp::Reverse;\n\n/* Initialize a heap */\n// Initialize a min heap\nlet mut min_heap = BinaryHeap::<Reverse<i32>>::new();\n// Initialize a max heap\nlet mut max_heap = BinaryHeap::new();\n\n/* Push elements into the heap */\nmax_heap.push(1);\nmax_heap.push(3);\nmax_heap.push(2);\nmax_heap.push(5);\nmax_heap.push(4);\n\n/* Get the heap top element */\nlet peek = max_heap.peek().unwrap();  // 5\n\n/* Remove the heap top element */\n// The removed elements will form a descending sequence\nlet peek = max_heap.pop().unwrap();   // 5\nlet peek = max_heap.pop().unwrap();   // 4\nlet peek = max_heap.pop().unwrap();   // 3\nlet peek = max_heap.pop().unwrap();   // 2\nlet peek = max_heap.pop().unwrap();   // 1\n\n/* Get the heap size */\nlet size = max_heap.len();\n\n/* Check if the heap is empty */\nlet is_empty = max_heap.is_empty();\n\n/* Build a heap from an input list */\nlet min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]);\n
heap.c
// C does not provide a built-in Heap class\n
heap.kt
/* Initialize a heap */\n// Initialize a min heap\nvar minHeap = PriorityQueue<Int>()\n// Initialize a max heap (use lambda expression to modify Comparator)\nval maxHeap = PriorityQueue { a: Int, b: Int -> b - a }\n\n/* Push elements into the heap */\nmaxHeap.offer(1)\nmaxHeap.offer(3)\nmaxHeap.offer(2)\nmaxHeap.offer(5)\nmaxHeap.offer(4)\n\n/* Get the heap top element */\nvar peek = maxHeap.peek() // 5\n\n/* Remove the heap top element */\n// The removed elements will form a descending sequence\npeek = maxHeap.poll() // 5\npeek = maxHeap.poll() // 4\npeek = maxHeap.poll() // 3\npeek = maxHeap.poll() // 2\npeek = maxHeap.poll() // 1\n\n/* Get the heap size */\nval size = maxHeap.size\n\n/* Check if the heap is empty */\nval isEmpty = maxHeap.isEmpty()\n\n/* Build a heap from an input list */\nminHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4))\n
heap.rb
# Ruby does not provide a built-in Heap class\n
Code Visualization

Full Screen >

","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#812-implementation-of-the-heap","level":2,"title":"8.1.2   Implementation of the Heap","text":"

The following implementation is of a max heap. To convert it to a min heap, simply invert all size logic comparisons (for example, replace \\(\\geq\\) with \\(\\leq\\)). Interested readers are encouraged to implement this on their own.

","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#1-heap-storage-and-representation","level":3,"title":"1.   Heap Storage and Representation","text":"

As mentioned in the \"Binary Tree\" chapter, complete binary trees are well-suited for array representation. Since heaps are a type of complete binary tree, we will use arrays to store heaps.

When representing a binary tree with an array, elements represent node values, and indexes represent node positions in the binary tree. Node pointers are implemented through index mapping formulas.

As shown in Figure 8-2, given an index \\(i\\), the index of its left child is \\(2i + 1\\), the index of its right child is \\(2i + 2\\), and the index of its parent is \\((i - 1) / 2\\) (floor division). When an index is out of bounds, it indicates a null node or that the node does not exist.

Figure 8-2   Representation and storage of heaps

We can encapsulate the index mapping formula into functions for convenient subsequent use:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
def left(self, i: int) -> int:\n    \"\"\"Get index of left child node\"\"\"\n    return 2 * i + 1\n\ndef right(self, i: int) -> int:\n    \"\"\"Get index of right child node\"\"\"\n    return 2 * i + 2\n\ndef parent(self, i: int) -> int:\n    \"\"\"Get index of parent node\"\"\"\n    return (i - 1) // 2  # Floor division\n
my_heap.cpp
/* Get index of left child node */\nint left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\nint right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\nint parent(int i) {\n    return (i - 1) / 2; // Floor division\n}\n
my_heap.java
/* Get index of left child node */\nint left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\nint right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\nint parent(int i) {\n    return (i - 1) / 2; // Floor division\n}\n
my_heap.cs
/* Get index of left child node */\nint Left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\nint Right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\nint Parent(int i) {\n    return (i - 1) / 2; // Floor division\n}\n
my_heap.go
/* Get index of left child node */\nfunc (h *maxHeap) left(i int) int {\n    return 2*i + 1\n}\n\n/* Get index of right child node */\nfunc (h *maxHeap) right(i int) int {\n    return 2*i + 2\n}\n\n/* Get index of parent node */\nfunc (h *maxHeap) parent(i int) int {\n    // Floor division\n    return (i - 1) / 2\n}\n
my_heap.swift
/* Get index of left child node */\nfunc left(i: Int) -> Int {\n    2 * i + 1\n}\n\n/* Get index of right child node */\nfunc right(i: Int) -> Int {\n    2 * i + 2\n}\n\n/* Get index of parent node */\nfunc parent(i: Int) -> Int {\n    (i - 1) / 2 // Floor division\n}\n
my_heap.js
/* Get index of left child node */\n#left(i) {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\n#right(i) {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\n#parent(i) {\n    return Math.floor((i - 1) / 2); // Floor division\n}\n
my_heap.ts
/* Get index of left child node */\nleft(i: number): number {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\nright(i: number): number {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\nparent(i: number): number {\n    return Math.floor((i - 1) / 2); // Floor division\n}\n
my_heap.dart
/* Get index of left child node */\nint _left(int i) {\n  return 2 * i + 1;\n}\n\n/* Get index of right child node */\nint _right(int i) {\n  return 2 * i + 2;\n}\n\n/* Get index of parent node */\nint _parent(int i) {\n  return (i - 1) ~/ 2; // Floor division\n}\n
my_heap.rs
/* Get index of left child node */\nfn left(i: usize) -> usize {\n    2 * i + 1\n}\n\n/* Get index of right child node */\nfn right(i: usize) -> usize {\n    2 * i + 2\n}\n\n/* Get index of parent node */\nfn parent(i: usize) -> usize {\n    (i - 1) / 2 // Floor division\n}\n
my_heap.c
/* Get index of left child node */\nint left(MaxHeap *maxHeap, int i) {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\nint right(MaxHeap *maxHeap, int i) {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\nint parent(MaxHeap *maxHeap, int i) {\n    return (i - 1) / 2; // Round down\n}\n
my_heap.kt
/* Get index of left child node */\nfun left(i: Int): Int {\n    return 2 * i + 1\n}\n\n/* Get index of right child node */\nfun right(i: Int): Int {\n    return 2 * i + 2\n}\n\n/* Get index of parent node */\nfun parent(i: Int): Int {\n    return (i - 1) / 2 // Floor division\n}\n
my_heap.rb
### Get left child index ###\ndef left(i)\n  2 * i + 1\nend\n\n### Get right child index ###\ndef right(i)\n  2 * i + 2\nend\n\n### Get parent node index ###\ndef parent(i)\n  (i - 1) / 2     # Floor division\nend\n
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#2-accessing-the-heap-top-element","level":3,"title":"2.   Accessing the Heap Top Element","text":"

The heap top element is the root node of the binary tree, which is also the first element of the list:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
def peek(self) -> int:\n    \"\"\"Access top element\"\"\"\n    return self.max_heap[0]\n
my_heap.cpp
/* Access top element */\nint peek() {\n    return maxHeap[0];\n}\n
my_heap.java
/* Access top element */\nint peek() {\n    return maxHeap.get(0);\n}\n
my_heap.cs
/* Access top element */\nint Peek() {\n    return maxHeap[0];\n}\n
my_heap.go
/* Access top element */\nfunc (h *maxHeap) peek() any {\n    return h.data[0]\n}\n
my_heap.swift
/* Access top element */\nfunc peek() -> Int {\n    maxHeap[0]\n}\n
my_heap.js
/* Access top element */\npeek() {\n    return this.#maxHeap[0];\n}\n
my_heap.ts
/* Access top element */\npeek(): number {\n    return this.maxHeap[0];\n}\n
my_heap.dart
/* Access top element */\nint peek() {\n  return _maxHeap[0];\n}\n
my_heap.rs
/* Access top element */\nfn peek(&self) -> Option<i32> {\n    self.max_heap.first().copied()\n}\n
my_heap.c
/* Access top element */\nint peek(MaxHeap *maxHeap) {\n    return maxHeap->data[0];\n}\n
my_heap.kt
/* Access top element */\nfun peek(): Int {\n    return maxHeap[0]\n}\n
my_heap.rb
### Access heap top element ###\ndef peek\n  @max_heap[0]\nend\n
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#3-inserting-an-element-into-the-heap","level":3,"title":"3.   Inserting an Element Into the Heap","text":"

Given an element val, we first add it to the bottom of the heap. After addition, since val may be larger than other elements in the heap, the heap's property may be violated. Therefore, it's necessary to repair the path from the inserted node to the root node. This operation is called heapify.

Starting from the inserted node, perform heapify from bottom to top. As shown in Figure 8-3, we compare the inserted node with its parent node, and if the inserted node is larger, swap them. Then continue this operation, repairing nodes in the heap from bottom to top until we pass the root node or encounter a node that does not need swapping.

<1><2><3><4><5><6><7><8><9>

Figure 8-3   Steps of inserting an element into the heap

Given a total of \\(n\\) nodes, the tree height is \\(O(\\log n)\\). Thus, the number of loop iterations in the heapify operation is at most \\(O(\\log n)\\), making the time complexity of the element insertion operation \\(O(\\log n)\\). The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
def push(self, val: int):\n    \"\"\"Element enters heap\"\"\"\n    # Add node\n    self.max_heap.append(val)\n    # Heapify from bottom to top\n    self.sift_up(self.size() - 1)\n\ndef sift_up(self, i: int):\n    \"\"\"Starting from node i, heapify from bottom to top\"\"\"\n    while True:\n        # Get parent node of node i\n        p = self.parent(i)\n        # When \"crossing root node\" or \"node needs no repair\", end heapify\n        if p < 0 or self.max_heap[i] <= self.max_heap[p]:\n            break\n        # Swap two nodes\n        self.swap(i, p)\n        # Loop upward heapify\n        i = p\n
my_heap.cpp
/* Element enters heap */\nvoid push(int val) {\n    // Add node\n    maxHeap.push_back(val);\n    // Heapify from bottom to top\n    siftUp(size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nvoid siftUp(int i) {\n    while (true) {\n        // Get parent node of node i\n        int p = parent(i);\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || maxHeap[i] <= maxHeap[p])\n            break;\n        // Swap two nodes\n        swap(maxHeap[i], maxHeap[p]);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.java
/* Element enters heap */\nvoid push(int val) {\n    // Add node\n    maxHeap.add(val);\n    // Heapify from bottom to top\n    siftUp(size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nvoid siftUp(int i) {\n    while (true) {\n        // Get parent node of node i\n        int p = parent(i);\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || maxHeap.get(i) <= maxHeap.get(p))\n            break;\n        // Swap two nodes\n        swap(i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.cs
/* Element enters heap */\nvoid Push(int val) {\n    // Add node\n    maxHeap.Add(val);\n    // Heapify from bottom to top\n    SiftUp(Size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nvoid SiftUp(int i) {\n    while (true) {\n        // Get parent node of node i\n        int p = Parent(i);\n        // If 'past root node' or 'node needs no repair', end heapify\n        if (p < 0 || maxHeap[i] <= maxHeap[p])\n            break;\n        // Swap two nodes\n        Swap(i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.go
/* Element enters heap */\nfunc (h *maxHeap) push(val any) {\n    // Add node\n    h.data = append(h.data, val)\n    // Heapify from bottom to top\n    h.siftUp(len(h.data) - 1)\n}\n\n/* Starting from node i, heapify from bottom to top */\nfunc (h *maxHeap) siftUp(i int) {\n    for true {\n        // Get parent node of node i\n        p := h.parent(i)\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if p < 0 || h.data[i].(int) <= h.data[p].(int) {\n            break\n        }\n        // Swap two nodes\n        h.swap(i, p)\n        // Loop upward heapify\n        i = p\n    }\n}\n
my_heap.swift
/* Element enters heap */\nfunc push(val: Int) {\n    // Add node\n    maxHeap.append(val)\n    // Heapify from bottom to top\n    siftUp(i: size() - 1)\n}\n\n/* Starting from node i, heapify from bottom to top */\nfunc siftUp(i: Int) {\n    var i = i\n    while true {\n        // Get parent node of node i\n        let p = parent(i: i)\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if p < 0 || maxHeap[i] <= maxHeap[p] {\n            break\n        }\n        // Swap two nodes\n        swap(i: i, j: p)\n        // Loop upward heapify\n        i = p\n    }\n}\n
my_heap.js
/* Element enters heap */\npush(val) {\n    // Add node\n    this.#maxHeap.push(val);\n    // Heapify from bottom to top\n    this.#siftUp(this.size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\n#siftUp(i) {\n    while (true) {\n        // Get parent node of node i\n        const p = this.#parent(i);\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break;\n        // Swap two nodes\n        this.#swap(i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.ts
/* Element enters heap */\npush(val: number): void {\n    // Add node\n    this.maxHeap.push(val);\n    // Heapify from bottom to top\n    this.siftUp(this.size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nsiftUp(i: number): void {\n    while (true) {\n        // Get parent node of node i\n        const p = this.parent(i);\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break;\n        // Swap two nodes\n        this.swap(i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.dart
/* Element enters heap */\nvoid push(int val) {\n  // Add node\n  _maxHeap.add(val);\n  // Heapify from bottom to top\n  siftUp(size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nvoid siftUp(int i) {\n  while (true) {\n    // Get parent node of node i\n    int p = _parent(i);\n    // When \"crossing root node\" or \"node needs no repair\", end heapify\n    if (p < 0 || _maxHeap[i] <= _maxHeap[p]) {\n      break;\n    }\n    // Swap two nodes\n    _swap(i, p);\n    // Loop upward heapify\n    i = p;\n  }\n}\n
my_heap.rs
/* Element enters heap */\nfn push(&mut self, val: i32) {\n    // Add node\n    self.max_heap.push(val);\n    // Heapify from bottom to top\n    self.sift_up(self.size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nfn sift_up(&mut self, mut i: usize) {\n    loop {\n        // Node i is already the heap root, end heapification\n        if i == 0 {\n            break;\n        }\n        // Get parent node of node i\n        let p = Self::parent(i);\n        // When \"node needs no repair\", end heapification\n        if self.max_heap[i] <= self.max_heap[p] {\n            break;\n        }\n        // Swap two nodes\n        self.swap(i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.c
/* Element enters heap */\nvoid push(MaxHeap *maxHeap, int val) {\n    // By default, should not add this many nodes\n    if (maxHeap->size == MAX_SIZE) {\n        printf(\"heap is full!\");\n        return;\n    }\n    // Add node\n    maxHeap->data[maxHeap->size] = val;\n    maxHeap->size++;\n\n    // Heapify from bottom to top\n    siftUp(maxHeap, maxHeap->size - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nvoid siftUp(MaxHeap *maxHeap, int i) {\n    while (true) {\n        // Get parent node of node i\n        int p = parent(maxHeap, i);\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) {\n            break;\n        }\n        // Swap two nodes\n        swap(maxHeap, i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.kt
/* Element enters heap */\nfun push(_val: Int) {\n    // Add node\n    maxHeap.add(_val)\n    // Heapify from bottom to top\n    siftUp(size() - 1)\n}\n\n/* Starting from node i, heapify from bottom to top */\nfun siftUp(it: Int) {\n    // Kotlin function parameters are immutable, so create temporary variable\n    var i = it\n    while (true) {\n        // Get parent node of node i\n        val p = parent(i)\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || maxHeap[i] <= maxHeap[p]) break\n        // Swap two nodes\n        swap(i, p)\n        // Loop upward heapify\n        i = p\n    }\n}\n
my_heap.rb
### Push element to heap ###\ndef push(val)\n  # Add node\n  @max_heap << val\n  # Heapify from bottom to top\n  sift_up(size - 1)\nend\n\n### Heapify from node i, bottom to top ###\ndef sift_up(i)\n  loop do\n    # Get parent node of node i\n    p = parent(i)\n    # When \"crossing root node\" or \"node needs no repair\", end heapify\n    break if p < 0 || @max_heap[i] <= @max_heap[p]\n    # Swap two nodes\n    swap(i, p)\n    # Loop upward heapify\n    i = p\n  end\nend\n
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#4-removing-the-heap-top-element","level":3,"title":"4.   Removing the Heap Top Element","text":"

The heap top element is the root node of the binary tree, which is the first element of the list. If we directly remove the first element from the list, all node indexes in the binary tree would change, making subsequent repair with heapify difficult. To minimize changes in element indexes, we use the following steps.

  1. Swap the heap top element with the heap bottom element (swap the root node with the rightmost leaf node).
  2. After swapping, remove the heap bottom from the list (note that since we've swapped, we're actually removing the original heap top element).
  3. Starting from the root node, perform heapify from top to bottom.

As shown in Figure 8-4, the direction of \"top-to-bottom heapify\" is opposite to \"bottom-to-top heapify\". We compare the root node's value with its two children and swap it with the largest child. Then loop this operation until we pass a leaf node or encounter a node that doesn't need swapping.

<1><2><3><4><5><6><7><8><9><10>

Figure 8-4   Steps of removing the heap top element

Similar to the element insertion operation, the time complexity of the heap top element removal operation is also \\(O(\\log n)\\). The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
def pop(self) -> int:\n    \"\"\"Element exits heap\"\"\"\n    # Handle empty case\n    if self.is_empty():\n        raise IndexError(\"Heap is empty\")\n    # Swap root node with rightmost leaf node (swap first element with last element)\n    self.swap(0, self.size() - 1)\n    # Delete node\n    val = self.max_heap.pop()\n    # Heapify from top to bottom\n    self.sift_down(0)\n    # Return top element\n    return val\n\ndef sift_down(self, i: int):\n    \"\"\"Starting from node i, heapify from top to bottom\"\"\"\n    while True:\n        # Find node with largest value among i, l, r, denoted as ma\n        l, r, ma = self.left(i), self.right(i), i\n        if l < self.size() and self.max_heap[l] > self.max_heap[ma]:\n            ma = l\n        if r < self.size() and self.max_heap[r] > self.max_heap[ma]:\n            ma = r\n        # If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        if ma == i:\n            break\n        # Swap two nodes\n        self.swap(i, ma)\n        # Loop downward heapify\n        i = ma\n
my_heap.cpp
/* Element exits heap */\nvoid pop() {\n    // Handle empty case\n    if (isEmpty()) {\n        throw out_of_range(\"Heap is empty\");\n    }\n    // Delete node\n    swap(maxHeap[0], maxHeap[size() - 1]);\n    // Remove node\n    maxHeap.pop_back();\n    // Return top element\n    siftDown(0);\n}\n\n/* Starting from node i, heapify from top to bottom */\nvoid siftDown(int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = left(i), r = right(i), ma = i;\n        if (l < size() && maxHeap[l] > maxHeap[ma])\n            ma = l;\n        if (r < size() && maxHeap[r] > maxHeap[ma])\n            ma = r;\n        // Swap two nodes\n        if (ma == i)\n            break;\n        swap(maxHeap[i], maxHeap[ma]);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.java
/* Element exits heap */\nint pop() {\n    // Handle empty case\n    if (isEmpty())\n        throw new IndexOutOfBoundsException();\n    // Delete node\n    swap(0, size() - 1);\n    // Remove node\n    int val = maxHeap.remove(size() - 1);\n    // Return top element\n    siftDown(0);\n    // Return heap top element\n    return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\nvoid siftDown(int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = left(i), r = right(i), ma = i;\n        if (l < size() && maxHeap.get(l) > maxHeap.get(ma))\n            ma = l;\n        if (r < size() && maxHeap.get(r) > maxHeap.get(ma))\n            ma = r;\n        // Swap two nodes\n        if (ma == i)\n            break;\n        // Swap two nodes\n        swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.cs
/* Element exits heap */\nint Pop() {\n    // Handle empty case\n    if (IsEmpty())\n        throw new IndexOutOfRangeException();\n    // Delete node\n    Swap(0, Size() - 1);\n    // Remove node\n    int val = maxHeap.Last();\n    maxHeap.RemoveAt(Size() - 1);\n    // Return top element\n    SiftDown(0);\n    // Return heap top element\n    return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\nvoid SiftDown(int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = Left(i), r = Right(i), ma = i;\n        if (l < Size() && maxHeap[l] > maxHeap[ma])\n            ma = l;\n        if (r < Size() && maxHeap[r] > maxHeap[ma])\n            ma = r;\n        // If 'node i is largest' or 'past leaf node', end heapify\n        if (ma == i) break;\n        // Swap two nodes\n        Swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.go
/* Element exits heap */\nfunc (h *maxHeap) pop() any {\n    // Handle empty case\n    if h.isEmpty() {\n        fmt.Println(\"error\")\n        return nil\n    }\n    // Delete node\n    h.swap(0, h.size()-1)\n    // Remove node\n    val := h.data[len(h.data)-1]\n    h.data = h.data[:len(h.data)-1]\n    // Return top element\n    h.siftDown(0)\n\n    // Return heap top element\n    return val\n}\n\n/* Starting from node i, heapify from top to bottom */\nfunc (h *maxHeap) siftDown(i int) {\n    for true {\n        // Find node with maximum value among nodes i, l, r, denoted as max\n        l, r, max := h.left(i), h.right(i), i\n        if l < h.size() && h.data[l].(int) > h.data[max].(int) {\n            max = l\n        }\n        if r < h.size() && h.data[r].(int) > h.data[max].(int) {\n            max = r\n        }\n        // Swap two nodes\n        if max == i {\n            break\n        }\n        // Swap two nodes\n        h.swap(i, max)\n        // Loop downwards heapification\n        i = max\n    }\n}\n
my_heap.swift
/* Element exits heap */\nfunc pop() -> Int {\n    // Handle empty case\n    if isEmpty() {\n        fatalError(\"Heap is empty\")\n    }\n    // Delete node\n    swap(i: 0, j: size() - 1)\n    // Remove node\n    let val = maxHeap.remove(at: size() - 1)\n    // Return top element\n    siftDown(i: 0)\n    // Return heap top element\n    return val\n}\n\n/* Starting from node i, heapify from top to bottom */\nfunc siftDown(i: Int) {\n    var i = i\n    while true {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let l = left(i: i)\n        let r = right(i: i)\n        var ma = i\n        if l < size(), maxHeap[l] > maxHeap[ma] {\n            ma = l\n        }\n        if r < size(), maxHeap[r] > maxHeap[ma] {\n            ma = r\n        }\n        // Swap two nodes\n        if ma == i {\n            break\n        }\n        // Swap two nodes\n        swap(i: i, j: ma)\n        // Loop downwards heapification\n        i = ma\n    }\n}\n
my_heap.js
/* Element exits heap */\npop() {\n    // Handle empty case\n    if (this.isEmpty()) throw new Error('Heap is empty');\n    // Delete node\n    this.#swap(0, this.size() - 1);\n    // Remove node\n    const val = this.#maxHeap.pop();\n    // Return top element\n    this.#siftDown(0);\n    // Return heap top element\n    return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\n#siftDown(i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        const l = this.#left(i),\n            r = this.#right(i);\n        let ma = i;\n        if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l;\n        if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r;\n        // Swap two nodes\n        if (ma === i) break;\n        // Swap two nodes\n        this.#swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.ts
/* Element exits heap */\npop(): number {\n    // Handle empty case\n    if (this.isEmpty()) throw new RangeError('Heap is empty.');\n    // Delete node\n    this.swap(0, this.size() - 1);\n    // Remove node\n    const val = this.maxHeap.pop();\n    // Return top element\n    this.siftDown(0);\n    // Return heap top element\n    return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\nsiftDown(i: number): void {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        const l = this.left(i),\n            r = this.right(i);\n        let ma = i;\n        if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l;\n        if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r;\n        // Swap two nodes\n        if (ma === i) break;\n        // Swap two nodes\n        this.swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.dart
/* Element exits heap */\nint pop() {\n  // Handle empty case\n  if (isEmpty()) throw Exception('Heap is empty');\n  // Delete node\n  _swap(0, size() - 1);\n  // Remove node\n  int val = _maxHeap.removeLast();\n  // Return top element\n  siftDown(0);\n  // Return heap top element\n  return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\nvoid siftDown(int i) {\n  while (true) {\n    // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n    int l = _left(i);\n    int r = _right(i);\n    int ma = i;\n    if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l;\n    if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r;\n    // Swap two nodes\n    if (ma == i) break;\n    // Swap two nodes\n    _swap(i, ma);\n    // Loop downwards heapification\n    i = ma;\n  }\n}\n
my_heap.rs
/* Element exits heap */\nfn pop(&mut self) -> i32 {\n    // Handle empty case\n    if self.is_empty() {\n        panic!(\"index out of bounds\");\n    }\n    // Delete node\n    self.swap(0, self.size() - 1);\n    // Remove node\n    let val = self.max_heap.pop().unwrap();\n    // Return top element\n    self.sift_down(0);\n    // Return heap top element\n    val\n}\n\n/* Starting from node i, heapify from top to bottom */\nfn sift_down(&mut self, mut i: usize) {\n    loop {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let (l, r, mut ma) = (Self::left(i), Self::right(i), i);\n        if l < self.size() && self.max_heap[l] > self.max_heap[ma] {\n            ma = l;\n        }\n        if r < self.size() && self.max_heap[r] > self.max_heap[ma] {\n            ma = r;\n        }\n        // Swap two nodes\n        if ma == i {\n            break;\n        }\n        // Swap two nodes\n        self.swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.c
/* Element exits heap */\nint pop(MaxHeap *maxHeap) {\n    // Handle empty case\n    if (isEmpty(maxHeap)) {\n        printf(\"heap is empty!\");\n        return INT_MAX;\n    }\n    // Delete node\n    swap(maxHeap, 0, size(maxHeap) - 1);\n    // Remove node\n    int val = maxHeap->data[maxHeap->size - 1];\n    maxHeap->size--;\n    // Return top element\n    siftDown(maxHeap, 0);\n\n    // Return heap top element\n    return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\nvoid siftDown(MaxHeap *maxHeap, int i) {\n    while (true) {\n        // Find node with maximum value among nodes i, l, r, denoted as max\n        int l = left(maxHeap, i);\n        int r = right(maxHeap, i);\n        int max = i;\n        if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) {\n            max = l;\n        }\n        if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) {\n            max = r;\n        }\n        // Swap two nodes\n        if (max == i) {\n            break;\n        }\n        // Swap two nodes\n        swap(maxHeap, i, max);\n        // Loop downwards heapification\n        i = max;\n    }\n}\n
my_heap.kt
/* Element exits heap */\nfun pop(): Int {\n    // Handle empty case\n    if (isEmpty()) throw IndexOutOfBoundsException()\n    // Delete node\n    swap(0, size() - 1)\n    // Remove node\n    val _val = maxHeap.removeAt(size() - 1)\n    // Return top element\n    siftDown(0)\n    // Return heap top element\n    return _val\n}\n\n/* Starting from node i, heapify from top to bottom */\nfun siftDown(it: Int) {\n    // Kotlin function parameters are immutable, so create temporary variable\n    var i = it\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        val l = left(i)\n        val r = right(i)\n        var ma = i\n        if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l\n        if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r\n        // Swap two nodes\n        if (ma == i) break\n        // Swap two nodes\n        swap(i, ma)\n        // Loop downwards heapification\n        i = ma\n    }\n}\n
my_heap.rb
### Pop element from heap ###\ndef pop\n  # Handle empty case\n  raise IndexError, \"Heap is empty\" if is_empty?\n  # Delete node\n  swap(0, size - 1)\n  # Remove node\n  val = @max_heap.pop\n  # Return top element\n  sift_down(0)\n  # Return heap top element\n  val\nend\n\n### Heapify from node i, top to bottom ###\ndef sift_down(i)\n  loop do\n    # If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n    l, r, ma = left(i), right(i), i\n    ma = l if l < size && @max_heap[l] > @max_heap[ma]\n    ma = r if r < size && @max_heap[r] > @max_heap[ma]\n\n    # Swap two nodes\n    break if ma == i\n\n    # Swap two nodes\n    swap(i, ma)\n    # Loop downwards heapification\n    i = ma\n  end\nend\n
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#813-common-applications-of-heaps","level":2,"title":"8.1.3   Common Applications of Heaps","text":"
  • Priority queue: Heaps are typically the preferred data structure for implementing priority queues, with both enqueue and dequeue operations having a time complexity of \\(O(\\log n)\\), and the heap construction operation having \\(O(n)\\), all of which are highly efficient.
  • Heap sort: Given a set of data, we can build a heap with them and then continuously perform element removal operations to obtain sorted data. However, we usually use a more elegant approach to implement heap sort, as detailed in the \"Heap Sort\" chapter.
  • Getting the largest \\(k\\) elements: This is a classic algorithm problem and also a typical application, such as selecting the top 10 trending news for Weibo hot search, selecting the top 10 best-selling products, etc.
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/summary/","level":1,"title":"8.4   Summary","text":"","path":["Chapter 8. Heap","8.4   Summary"],"tags":[]},{"location":"chapter_heap/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • A heap is a complete binary tree that can be categorized as a max heap or min heap based on its property. The heap top element of a max heap (min heap) is the largest (smallest).
  • A priority queue is defined as a queue with priority sorting, typically implemented using heaps.
  • Common heap operations and their corresponding time complexities include: element insertion \\(O(\\log n)\\), heap top element removal \\(O(\\log n)\\), and accessing the heap top element \\(O(1)\\).
  • Complete binary trees are well-suited for array representation, so we typically use arrays to store heaps.
  • Heapify operations are used to maintain the heap property and are employed in both element insertion and removal operations.
  • The time complexity of building a heap with \\(n\\) input elements can be optimized to \\(O(n)\\), which is highly efficient.
  • Top-k is a classic algorithm problem that can be efficiently solved using the heap data structure, with a time complexity of \\(O(n \\log k)\\).
","path":["Chapter 8. Heap","8.4   Summary"],"tags":[]},{"location":"chapter_heap/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Are the \"heap\" in data structures and the \"heap\" in memory management the same concept?

The two are not the same concept; they just happen to share the name \"heap.\" The heap in computer system memory is part of dynamic memory allocation, where programs can use it to store data during runtime. Programs can request a certain amount of heap memory to store complex structures such as objects and arrays. When this data is no longer needed, the program needs to release this memory to prevent memory leaks. Compared to stack memory, heap memory management and usage require more caution, as improper use can lead to issues such as memory leaks and dangling pointers.

","path":["Chapter 8. Heap","8.4   Summary"],"tags":[]},{"location":"chapter_heap/top_k/","level":1,"title":"8.3   Top-K Problem","text":"

Question

Given an unordered array nums of length \\(n\\), return the largest \\(k\\) elements in the array.

For this problem, we'll first introduce two solutions with relatively straightforward approaches, then introduce a more efficient heap-based solution.

","path":["Chapter 8. Heap","8.3   Top-K Problem"],"tags":[]},{"location":"chapter_heap/top_k/#831-method-1-iterative-selection","level":2,"title":"8.3.1   Method 1: Iterative Selection","text":"

We can perform \\(k\\) rounds of traversal as shown in Figure 8-6, extracting the \\(1^{st}\\), \\(2^{nd}\\), \\(\\dots\\), \\(k^{th}\\) largest elements in each round, with a time complexity of \\(O(nk)\\).

This method is only suitable when \\(k \\ll n\\), because when \\(k\\) is close to \\(n\\), the time complexity approaches \\(O(n^2)\\), which is very time-consuming.

Figure 8-6   Traversing to find the largest k elements

Tip

When \\(k = n\\), we can obtain a complete sorted sequence, which is equivalent to the \"selection sort\" algorithm.

","path":["Chapter 8. Heap","8.3   Top-K Problem"],"tags":[]},{"location":"chapter_heap/top_k/#832-method-2-sorting","level":2,"title":"8.3.2   Method 2: Sorting","text":"

As shown in Figure 8-7, we can first sort the array nums, then return the rightmost \\(k\\) elements, with a time complexity of \\(O(n \\log n)\\).

Clearly, this method \"overachieves\" the task, as we only need to find the largest \\(k\\) elements, without needing to sort the other elements.

Figure 8-7   Sorting to find the largest k elements

","path":["Chapter 8. Heap","8.3   Top-K Problem"],"tags":[]},{"location":"chapter_heap/top_k/#833-method-3-heap","level":2,"title":"8.3.3   Method 3: Heap","text":"

We can solve the Top-k problem more efficiently using heaps, with the process shown in Figure 8-8.

  1. Initialize a min heap, where the heap top element is the smallest.
  2. First, insert the first \\(k\\) elements of the array into the heap in sequence.
  3. Starting from the \\((k + 1)^{th}\\) element, if the current element is greater than the heap top element, remove the heap top element and insert the current element into the heap.
  4. After traversal is complete, the heap contains the largest \\(k\\) elements.
<1><2><3><4><5><6><7><8><9>

Figure 8-8   Finding the largest k elements using a heap

Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby top_k.py
def top_k_heap(nums: list[int], k: int) -> list[int]:\n    \"\"\"Find the largest k elements in array based on heap\"\"\"\n    # Initialize min heap\n    heap = []\n    # Enter the first k elements of array into heap\n    for i in range(k):\n        heapq.heappush(heap, nums[i])\n    # Starting from the (k+1)th element, maintain heap length as k\n    for i in range(k, len(nums)):\n        # If current element is greater than top element, top element exits heap, current element enters heap\n        if nums[i] > heap[0]:\n            heapq.heappop(heap)\n            heapq.heappush(heap, nums[i])\n    return heap\n
top_k.cpp
/* Find the largest k elements in array based on heap */\npriority_queue<int, vector<int>, greater<int>> topKHeap(vector<int> &nums, int k) {\n    // Python's heapq module implements min heap by default\n    priority_queue<int, vector<int>, greater<int>> heap;\n    // Enter the first k elements of array into heap\n    for (int i = 0; i < k; i++) {\n        heap.push(nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (int i = k; i < nums.size(); i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > heap.top()) {\n            heap.pop();\n            heap.push(nums[i]);\n        }\n    }\n    return heap;\n}\n
top_k.java
/* Find the largest k elements in array based on heap */\nQueue<Integer> topKHeap(int[] nums, int k) {\n    // Python's heapq module implements min heap by default\n    Queue<Integer> heap = new PriorityQueue<Integer>();\n    // Enter the first k elements of array into heap\n    for (int i = 0; i < k; i++) {\n        heap.offer(nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (int i = k; i < nums.length; i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > heap.peek()) {\n            heap.poll();\n            heap.offer(nums[i]);\n        }\n    }\n    return heap;\n}\n
top_k.cs
/* Find the largest k elements in array based on heap */\nPriorityQueue<int, int> TopKHeap(int[] nums, int k) {\n    // Python's heapq module implements min heap by default\n    PriorityQueue<int, int> heap = new();\n    // Enter the first k elements of array into heap\n    for (int i = 0; i < k; i++) {\n        heap.Enqueue(nums[i], nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (int i = k; i < nums.Length; i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > heap.Peek()) {\n            heap.Dequeue();\n            heap.Enqueue(nums[i], nums[i]);\n        }\n    }\n    return heap;\n}\n
top_k.go
/* Find the largest k elements in array based on heap */\nfunc topKHeap(nums []int, k int) *minHeap {\n    // Python's heapq module implements min heap by default\n    h := &minHeap{}\n    heap.Init(h)\n    // Enter the first k elements of array into heap\n    for i := 0; i < k; i++ {\n        heap.Push(h, nums[i])\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for i := k; i < len(nums); i++ {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if nums[i] > h.Top().(int) {\n            heap.Pop(h)\n            heap.Push(h, nums[i])\n        }\n    }\n    return h\n}\n
top_k.swift
/* Find the largest k elements in array based on heap */\nfunc topKHeap(nums: [Int], k: Int) -> [Int] {\n    // Initialize min heap and build heap with first k elements\n    var heap = Heap(nums.prefix(k))\n    // Starting from the (k+1)th element, maintain heap length as k\n    for i in nums.indices.dropFirst(k) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if nums[i] > heap.min()! {\n            _ = heap.removeMin()\n            heap.insert(nums[i])\n        }\n    }\n    return heap.unordered\n}\n
top_k.js
/* Element enters heap */\nfunction pushMinHeap(maxHeap, val) {\n    // Negate element\n    maxHeap.push(-val);\n}\n\n/* Element exits heap */\nfunction popMinHeap(maxHeap) {\n    // Negate element\n    return -maxHeap.pop();\n}\n\n/* Access top element */\nfunction peekMinHeap(maxHeap) {\n    // Negate element\n    return -maxHeap.peek();\n}\n\n/* Extract elements from heap */\nfunction getMinHeap(maxHeap) {\n    // Negate element\n    return maxHeap.getMaxHeap().map((num) => -num);\n}\n\n/* Find the largest k elements in array based on heap */\nfunction topKHeap(nums, k) {\n    // Python's heapq module implements min heap by default\n    // Note: We negate all heap elements to simulate min heap using max heap\n    const maxHeap = new MaxHeap([]);\n    // Enter the first k elements of array into heap\n    for (let i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (let i = k; i < nums.length; i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    // Return elements in heap\n    return getMinHeap(maxHeap);\n}\n
top_k.ts
/* Element enters heap */\nfunction pushMinHeap(maxHeap: MaxHeap, val: number): void {\n    // Negate element\n    maxHeap.push(-val);\n}\n\n/* Element exits heap */\nfunction popMinHeap(maxHeap: MaxHeap): number {\n    // Negate element\n    return -maxHeap.pop();\n}\n\n/* Access top element */\nfunction peekMinHeap(maxHeap: MaxHeap): number {\n    // Negate element\n    return -maxHeap.peek();\n}\n\n/* Extract elements from heap */\nfunction getMinHeap(maxHeap: MaxHeap): number[] {\n    // Negate element\n    return maxHeap.getMaxHeap().map((num: number) => -num);\n}\n\n/* Find the largest k elements in array based on heap */\nfunction topKHeap(nums: number[], k: number): number[] {\n    // Python's heapq module implements min heap by default\n    // Note: We negate all heap elements to simulate min heap using max heap\n    const maxHeap = new MaxHeap([]);\n    // Enter the first k elements of array into heap\n    for (let i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (let i = k; i < nums.length; i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    // Return elements in heap\n    return getMinHeap(maxHeap);\n}\n
top_k.dart
/* Find the largest k elements in array based on heap */\nMinHeap topKHeap(List<int> nums, int k) {\n  // Initialize min heap, push first k elements of array to heap\n  MinHeap heap = MinHeap(nums.sublist(0, k));\n  // Starting from the (k+1)th element, maintain heap length as k\n  for (int i = k; i < nums.length; i++) {\n    // If current element is greater than top element, top element exits heap, current element enters heap\n    if (nums[i] > heap.peek()) {\n      heap.pop();\n      heap.push(nums[i]);\n    }\n  }\n  return heap;\n}\n
top_k.rs
/* Find the largest k elements in array based on heap */\nfn top_k_heap(nums: Vec<i32>, k: usize) -> BinaryHeap<Reverse<i32>> {\n    // BinaryHeap is a max heap, use Reverse to negate elements to implement min heap\n    let mut heap = BinaryHeap::<Reverse<i32>>::new();\n    // Enter the first k elements of array into heap\n    for &num in nums.iter().take(k) {\n        heap.push(Reverse(num));\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for &num in nums.iter().skip(k) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if num > heap.peek().unwrap().0 {\n            heap.pop();\n            heap.push(Reverse(num));\n        }\n    }\n    heap\n}\n
top_k.c
/* Element enters heap */\nvoid pushMinHeap(MaxHeap *maxHeap, int val) {\n    // Negate element\n    push(maxHeap, -val);\n}\n\n/* Element exits heap */\nint popMinHeap(MaxHeap *maxHeap) {\n    // Negate element\n    return -pop(maxHeap);\n}\n\n/* Access top element */\nint peekMinHeap(MaxHeap *maxHeap) {\n    // Negate element\n    return -peek(maxHeap);\n}\n\n/* Extract elements from heap */\nint *getMinHeap(MaxHeap *maxHeap) {\n    // Negate all heap elements and store in res array\n    int *res = (int *)malloc(maxHeap->size * sizeof(int));\n    for (int i = 0; i < maxHeap->size; i++) {\n        res[i] = -maxHeap->data[i];\n    }\n    return res;\n}\n\n/* Extract elements from heap */\nint *getMinHeap(MaxHeap *maxHeap) {\n    // Negate all heap elements and store in res array\n    int *res = (int *)malloc(maxHeap->size * sizeof(int));\n    for (int i = 0; i < maxHeap->size; i++) {\n        res[i] = -maxHeap->data[i];\n    }\n    return res;\n}\n\n// Function to find k largest elements in array using heap\nint *topKHeap(int *nums, int sizeNums, int k) {\n    // Python's heapq module implements min heap by default\n    // Note: We negate all heap elements to simulate min heap using max heap\n    int *empty = (int *)malloc(0);\n    MaxHeap *maxHeap = newMaxHeap(empty, 0);\n    // Enter the first k elements of array into heap\n    for (int i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (int i = k; i < sizeNums; i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    int *res = getMinHeap(maxHeap);\n    // Free memory\n    delMaxHeap(maxHeap);\n    return res;\n}\n
top_k.kt
/* Find the largest k elements in array based on heap */\nfun topKHeap(nums: IntArray, k: Int): Queue<Int> {\n    // Python's heapq module implements min heap by default\n    val heap = PriorityQueue<Int>()\n    // Enter the first k elements of array into heap\n    for (i in 0..<k) {\n        heap.offer(nums[i])\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (i in k..<nums.size) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > heap.peek()) {\n            heap.poll()\n            heap.offer(nums[i])\n        }\n    }\n    return heap\n}\n
top_k.rb
### Find largest k elements in array using heap ###\ndef top_k_heap(nums, k)\n  # Python's heapq module implements min heap by default\n  # Note: We negate all heap elements to simulate min heap using max heap\n  max_heap = MaxHeap.new([])\n\n  # Enter the first k elements of array into heap\n  for i in 0...k\n    push_min_heap(max_heap, nums[i])\n  end\n\n  # Starting from the (k+1)th element, maintain heap length as k\n  for i in k...nums.length\n    # If current element is greater than top element, top element exits heap, current element enters heap\n    if nums[i] > peek_min_heap(max_heap)\n      pop_min_heap(max_heap)\n      push_min_heap(max_heap, nums[i])\n    end\n  end\n\n  get_min_heap(max_heap)\nend\n

A total of \\(n\\) rounds of heap insertions and removals are performed, with the heap's maximum length being \\(k\\), so the time complexity is \\(O(n \\log k)\\). This method is very efficient; when \\(k\\) is small, the time complexity approaches \\(O(n)\\); when \\(k\\) is large, the time complexity does not exceed \\(O(n \\log n)\\).

Additionally, this method is suitable for dynamic data stream scenarios. By continuously adding data, we can maintain the elements in the heap, thus achieving dynamic updates of the largest \\(k\\) elements.

","path":["Chapter 8. Heap","8.3   Top-K Problem"],"tags":[]},{"location":"chapter_hello_algo/","level":1,"title":"Before Starting","text":"

A few years ago, I shared the \"Sword for Offer\" problem solutions on LeetCode, receiving encouragement and support from many readers. During interactions with readers, the most frequently asked question I encountered was \"how to get started with algorithms.\" Gradually, I developed a keen interest in this question.

Diving straight into problem-solving seems to be the most popular approach—it's simple, direct, and effective. However, problem-solving is like playing Minesweeper: those with strong self-learning abilities can successfully defuse the mines one by one, while those with insufficient foundations may end up bruised and battered, retreating step by step in frustration. Reading through textbooks is also a common practice, but for job seekers, graduation theses, resume submissions, and preparations for written tests and interviews have already consumed most of their energy, making working through thick books an arduous challenge.

If you're facing similar struggles, then it's fortunate that this book has \"found\" you. This book is my answer to this question—even if it may not be the optimal solution, it is at least a positive attempt. While this book alone won't directly land you a job offer, it will guide you to explore the \"knowledge map\" of data structures and algorithms, help you understand the shapes, sizes, and distributions of different \"mines,\" and enable you to master various \"mine-clearing methods.\" With these skills, I believe you can tackle problems and read technical literature more confidently, gradually building a complete knowledge system.

I deeply agree with Professor Feynman's words: \"Knowledge isn't free. You have to pay attention.\" In this sense, this book is not entirely \"free.\" In order to live up to the precious \"attention\" you invest in this book, I will do my utmost and devote my greatest \"attention\" to completing this work.

I'm acutely aware of my limited knowledge and shallow expertise. Although the content of this book has been refined over a period of time, there are certainly still many errors, and I sincerely welcome critiques and corrections from teachers and fellow students.

Hello, Algorithms!

The advent of computers has brought tremendous changes to the world. With their high-speed computing capabilities and excellent programmability, they have become the ideal medium for executing algorithms and processing data. Whether it's the realistic graphics in video games, the intelligent decision-making in autonomous driving, AlphaGo's brilliant Go matches, or ChatGPT's natural interactions, these applications are all exquisite interpretations of algorithms on computers.

In fact, before the advent of computers, algorithms and data structures already existed in every corner of the world. Early algorithms were relatively simple, such as ancient counting methods and tool-making procedures. As civilization progressed, algorithms gradually became more refined and complex. From the ingenious craftsmanship of master artisans, to industrial products that liberate productive forces, to the scientific laws governing the operation of the universe, behind almost every ordinary or astonishing thing lies ingenious algorithmic thinking.

Similarly, data structures are everywhere: from large-scale social networks to small subway systems, many systems can be modeled as \"graphs\"; from a nation to a family, the primary organizational forms of society exhibit characteristics of \"trees\"; winter clothing is like a \"stack,\" where the first item put on is the last to be taken off; a badminton tube is like a \"queue,\" with items inserted at one end and retrieved from the other; a dictionary is like a \"hash table,\" enabling quick lookup of target entries.

This book aims to help readers understand the core concepts of algorithms and data structures through clear and accessible animated illustrations and runnable code examples, and to implement them through programming. Building on this foundation, the book endeavors to reveal the vivid manifestations of algorithms in the complex world and showcase the beauty of algorithms. I hope this book can be of help to you!

","path":["Before Starting"],"tags":[]},{"location":"chapter_introduction/","level":1,"title":"Chapter 1.   Encounter with Algorithms","text":"

Abstract

A young girl dances gracefully, intertwined with data, her skirt flowing with the melody of algorithms.

She invites you to dance with her. Follow her steps closely and enter the world of algorithms, full of logic and beauty.

","path":["Chapter 1. Encounter With Algorithms","Chapter 1.   Encounter with Algorithms"],"tags":[]},{"location":"chapter_introduction/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 1.1   Algorithms Are Everywhere
  • 1.2   What Is an Algorithm
  • 1.3   Summary
","path":["Chapter 1. Encounter With Algorithms","Chapter 1.   Encounter with Algorithms"],"tags":[]},{"location":"chapter_introduction/algorithms_are_everywhere/","level":1,"title":"1.1   Algorithms Are Everywhere","text":"

When we hear the term \"algorithm,\" we naturally think of mathematics. However, many algorithms do not involve complex mathematics but rely more on basic logic, which can be seen everywhere in our daily lives.

Before we start discussing about algorithms officially, there's an interesting fact worth sharing: you've learned many algorithms unconsciously and are used to applying them in your daily life. Here, I will give a few specific examples to prove this point.

Example 1: Looking Up a Dictionary. In an English dictionary, words are listed alphabetically. Assuming we're searching for a word that starts with the letter \\(r\\), this is typically done in the following way:

  1. Open the dictionary to about halfway and check the first vocabulary of the page, let's say the letter starts with \\(m\\).
  2. Since \\(r\\) comes after \\(m\\) in the alphabet, the first half can be ignored and the search space is narrowed down to the second half.
  3. Repeat steps 1. and 2. until you find the page where the word starts with \\(r\\).
<1><2><3><4><5>

Figure 1-1   Process of looking up a dictionary

Looking up a dictionary, an essential skill for elementary school students is actually the famous \"Binary Search\" algorithm. From a data structure perspective, we can consider the dictionary as a sorted \"array\"; from an algorithmic perspective, the series of actions taken to look up a word in the dictionary can be viewed as the algorithm \"Binary Search.\"

Example 2: Organizing Card Deck. When playing cards, we need to arrange the cards in our hands in ascending order, as shown in the following process.

  1. Divide the playing cards into \"ordered\" and \"unordered\" sections, assuming initially the leftmost card is already in order.
  2. Take out a card from the unordered section and insert it into the correct position in the ordered section; after this, the leftmost two cards are in order.
  3. Repeat step 2 until all cards are in order.

Figure 1-2   Process of sorting a deck of cards

The above method of organizing playing cards is practically the \"Insertion Sort\" algorithm, which is very efficient for small datasets. Many programming languages' sorting functions include the insertion sort.

Example 3: Making Change. Assume making a purchase of \\(69\\) at a supermarket. If you give the cashier \\(100\\), they will need to provide you with \\(31\\) in change. This process can be clearly understood as illustrated in Figure 1-3.

  1. The options are currencies valued below \\(31\\), including \\(1\\), \\(5\\), \\(10\\), and \\(20\\).
  2. Take out the largest \\(20\\) from the options, leaving \\(31 - 20 = 11\\).
  3. Take out the largest \\(10\\) from the remaining options, leaving \\(11 - 10 = 1\\).
  4. Take out the largest \\(1\\) from the remaining options, leaving \\(1 - 1 = 0\\).
  5. Complete change-making, the solution is \\(20 + 10 + 1 = 31\\).

Figure 1-3   Process of making change

In the steps described, we choose the best option at each stage by utilizing the largest denomination available, which leads to an effective change-making strategy. From a data structures and algorithms perspective, this approach is known as a \"Greedy\" algorithm.

From cooking a meal to interstellar travel, almost all problem-solving involves algorithms. The advent of computers allows us to store data structures in memory and write code to call the CPU and GPU to execute algorithms. In this way, we can transfer real-life problems to computers and solve various complex issues in a more efficient way.

Tip

If you are still confused about concepts like data structures, algorithms, arrays, and binary searches, I encourage you to keep reading. This book will gently guide you into the realm of understanding data structures and algorithms.

","path":["Chapter 1. Encounter With Algorithms","1.1   Algorithms Are Everywhere"],"tags":[]},{"location":"chapter_introduction/summary/","level":1,"title":"1.3   Summary","text":"","path":["Chapter 1. Encounter With Algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Algorithms are ubiquitous in daily life and are not distant, esoteric knowledge. In fact, we have already learned many algorithms unconsciously and use them to solve problems big and small in life.
  • The principle of looking up a dictionary is consistent with the binary search algorithm. Binary search embodies the important algorithmic idea of divide and conquer.
  • The process of organizing playing cards is very similar to the insertion sort algorithm. Insertion sort is suitable for sorting small datasets.
  • The steps of making change are essentially a greedy algorithm, where the best choice is made at each step based on the current situation.
  • An algorithm is a set of instructions or operational steps that solves a specific problem within a finite amount of time, while a data structure is the way computers organize and store data.
  • Data structures and algorithms are closely connected. Data structures are the foundation of algorithms, and algorithms breathe life into data structures.
  • We can compare data structures and algorithms to assembling building blocks. The blocks represent data, the shape and connection method of the blocks represent the data structure, and the steps to assemble the blocks correspond to the algorithm.
","path":["Chapter 1. Encounter With Algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: As a programmer, I have never used algorithms to solve problems in my daily work. Common algorithms are already encapsulated by programming languages and can be used directly. Does this mean that the problems in our work have not yet reached the level where algorithms are needed?

If we compare specific work skills to \"techniques\" in martial arts, then fundamental subjects should be more like \"internal skills\".

I believe the significance of learning algorithms (and other fundamental subjects) is not to implement them from scratch at work, but rather to be able to make professional reactions and judgments when solving problems based on the knowledge learned, thereby improving the overall quality of work. Here is a simple example. Every programming language has a built-in sorting function:

  • If we have not studied data structures and algorithms, we might simply feed any given data to this sorting function. It runs smoothly with good performance, and there doesn't seem to be any problem.
  • But if we have studied algorithms, we would know that the time complexity of the built-in sorting function is \\(O(n \\log n)\\). However, if the given data consists of integers with a fixed number of digits (such as student IDs), we can use the more efficient \"radix sort\", reducing the time complexity to \\(O(nk)\\), where \\(k\\) is the number of digits. When the data volume is very large, the saved running time can create significant value (reduced costs, improved experience, etc.).

In the field of engineering, a large number of problems are difficult to reach optimal solutions, and many problems are only solved \"approximately\". The difficulty of a problem depends on one hand on the nature of the problem itself, and on the other hand on the knowledge reserve of the person observing the problem. The more complete a person's knowledge and the more experience they have, the deeper their analysis of the problem will be, and the more elegantly the problem can be solved.

","path":["Chapter 1. Encounter With Algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/","level":1,"title":"1.2   What Is an Algorithm","text":"","path":["Chapter 1. Encounter With Algorithms","1.2   What Is an Algorithm"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#121-algorithm-definition","level":2,"title":"1.2.1   Algorithm Definition","text":"

An algorithm is a set of instructions or operational steps that solves a specific problem within a finite amount of time. It has the following characteristics.

  • The problem is well-defined, with clear input and output definitions.
  • It is feasible and can be completed within a finite number of steps, time, and memory space.
  • Each step has a definite meaning, and under the same input and operating conditions, the output is always the same.
","path":["Chapter 1. Encounter With Algorithms","1.2   What Is an Algorithm"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#122-data-structure-definition","level":2,"title":"1.2.2   Data Structure Definition","text":"

A data structure is a way of organizing and storing data, covering the data content, relationships between data, and methods for data operations. It has the following design objectives.

  • Occupy as little space as possible to save computer memory.
  • Data operations should be as fast as possible, covering data access, addition, deletion, update, etc.
  • Provide a concise data representation and logical information so that algorithms can run efficiently.

Data structure design is a process full of trade-offs. If we want to achieve improvements in one aspect, we often need to make compromises in another aspect. Here are two examples.

  • Compared to arrays, linked lists are more convenient for data addition and deletion operations but sacrifice data access speed.
  • Compared to linked lists, graphs provide richer logical information but require larger memory space.
","path":["Chapter 1. Encounter With Algorithms","1.2   What Is an Algorithm"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#123-the-relationship-between-data-structures-and-algorithms","level":2,"title":"1.2.3   The Relationship Between Data Structures and Algorithms","text":"

As shown in Figure 1-4, data structures and algorithms are highly related and tightly coupled, specifically manifested in the following three aspects.

  • Data structures are the foundation of algorithms. Data structures provide algorithms with structured storage of data and methods for operating on data.
  • Algorithms breathe life into data structures. Data structures themselves only store data information; combined with algorithms, they can solve specific problems.
  • Algorithms can usually be implemented based on different data structures, but execution efficiency may vary greatly. Choosing the appropriate data structure is key.

Figure 1-4   The relationship between data structures and algorithms

Data structures and algorithms are like assembling building blocks as shown in Figure 1-5. A set of building blocks, in addition to containing many parts, also comes with detailed assembly instructions. By following the instructions step by step, we can assemble an exquisite building block model.

Figure 1-5   Assembling blocks

The detailed correspondence between the two is shown in Table 1-1.

Table 1-1   Comparing data structures and algorithms to assembling building blocks

Data structures and algorithms Assembling building blocks Input data Unassembled building blocks Data structure Organization form of building blocks, including shape, size, connection method, etc. Algorithm A series of operational steps to assemble the blocks into the target form Output data Building block model

It is worth noting that data structures and algorithms are independent of programming languages. For this reason, this book is able to provide implementations based on multiple programming languages.

Conventional abbreviation

In actual discussions, we usually abbreviate \"data structures and algorithms\" as \"algorithms\". For example, the well-known LeetCode algorithm problems actually examine knowledge of both data structures and algorithms.

","path":["Chapter 1. Encounter With Algorithms","1.2   What Is an Algorithm"],"tags":[]},{"location":"chapter_preface/","level":1,"title":"Chapter 0.   Preface","text":"

Abstract

Algorithms are like a beautiful symphony, each line of code flows like a melody.

May this book gently resonate in your mind, leaving a unique and profound melody.

","path":["Chapter 0. Preface","Chapter 0.   Preface"],"tags":[]},{"location":"chapter_preface/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 0.1   About This Book
  • 0.2   How to Use This Book
  • 0.3   Summary
","path":["Chapter 0. Preface","Chapter 0.   Preface"],"tags":[]},{"location":"chapter_preface/about_the_book/","level":1,"title":"0.1   About This Book","text":"

This project aims to create an open-source, free, beginner-friendly introductory tutorial on data structures and algorithms.

  • The entire book uses animated illustrations, with clear and easy-to-understand content and a smooth learning curve, guiding beginners to explore the knowledge map of data structures and algorithms.
  • The source code can be run with one click, helping readers improve their programming skills through practice and understand how algorithms work and the underlying implementation of data structures.
  • We encourage readers to learn from each other, and everyone is welcome to ask questions and share insights in the comments section, making progress together through discussion and exchange.
","path":["Chapter 0. Preface","0.1   About This Book"],"tags":[]},{"location":"chapter_preface/about_the_book/#011-target-audience","level":2,"title":"0.1.1   Target Audience","text":"

If you are an algorithm beginner who has never been exposed to algorithms, or if you already have some problem-solving experience and have a vague understanding of data structures and algorithms, oscillating between knowing and not knowing, then this book is tailor-made for you!

If you have already accumulated a certain amount of problem-solving experience and are familiar with most question types, this book can help you review and organize your algorithm knowledge system, and the repository's source code can be used as a \"problem-solving toolkit\" or \"algorithm dictionary.\"

If you are an algorithm \"expert,\" we look forward to receiving your valuable suggestions, or participating in creation together.

Prerequisites

You need to have at least a programming foundation in any language, and be able to read and write simple code.

","path":["Chapter 0. Preface","0.1   About This Book"],"tags":[]},{"location":"chapter_preface/about_the_book/#012-content-structure","level":2,"title":"0.1.2   Content Structure","text":"

The main content of this book is shown in Figure 0-1.

  • Complexity analysis: Evaluation dimensions and methods for data structures and algorithms. Methods for calculating time complexity and space complexity, common types, examples, etc.
  • Data structures: Classification methods for basic data types and data structures. The definition, advantages and disadvantages, common operations, common types, typical applications, implementation methods, etc. of data structures such as arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs.
  • Algorithms: The definition, advantages and disadvantages, efficiency, application scenarios, problem-solving steps, and example problems of algorithms such as searching, sorting, divide and conquer, backtracking, dynamic programming, and greedy algorithms.

Figure 0-1   Main content of this book

","path":["Chapter 0. Preface","0.1   About This Book"],"tags":[]},{"location":"chapter_preface/about_the_book/#013-acknowledgements","level":2,"title":"0.1.3   Acknowledgements","text":"

This book has been continuously improved through the joint efforts of many contributors in the open-source community. Thanks to every contributor who invested time and effort, they are (in the order automatically generated by GitHub): krahets, coderonion, Gonglja, nuomi1, Reanon, justin-tse, hpstory, danielsss, curtishd, night-cruise, S-N-O-R-L-A-X, rongyi, msk397, gvenusleo, khoaxuantu, rivertwilight, K3v123, gyt95, zhuoqinyue, yuelinxin, Zuoxun, mingXta, Phoenix0415, FangYuan33, GN-Yu, longsizhuo, IsChristina, xBLACKICEx, guowei-gong, Cathay-Chen, pengchzn, QiLOL, magentaqin, hello-ikun, JoseHung, qualifier1024, thomasq0, sunshinesDL, L-Super, Guanngxu, Transmigration-zhou, WSL0809, Slone123c, lhxsm, yuan0221, what-is-me, Shyam-Chen, theNefelibatas, longranger2, codeberg-user, xiongsp, JeffersonHuang, prinpal, seven1240, Wonderdch, malone6, xiaomiusa87, gaofer, bluebean-cloud, a16su, SamJin98, hongyun-robot, nanlei, XiaChuerwu, yd-j, iron-irax, mgisr, steventimes, junminhong, heshuyue, danny900714, MolDuM, Nigh, Dr-XYZ, XC-Zero, reeswell, PXG-XPG, NI-SW, Horbin-Magician, Enlightenus, YangXuanyi, beatrix-chan, DullSword, xjr7670, jiaxianhua, qq909244296, iStig, boloboloda, hts0000, gledfish, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, linyejoe2, liuxjerry, llql1211, fbigm, echo1937, szu17dmy, dshlstarr, Yucao-cy, coderlef, czruby, bongbongbakudan, beintentional, ZongYangL, ZhongYuuu, ZhongGuanbin, hezhizhen, linzeyan, ZJKung, luluxia, xb534, ztkuaikuai, yw-1021, ElaBosak233, baagod, zhouLion, yishangzhang, yi427, yanedie, yabo083, weibk, wangwang105, th1nk3r-ing, tao363, 4yDX3906, syd168, sslmj2020, smilelsb, siqyka, selear, sdshaoda, Xi-Row, popozhu, nuquist19, noobcodemaker, XiaoK29, chadyi, lyl625760, lucaswangdev, 0130w, shanghai-Jerry, EJackYang, Javesun99, eltociear, lipusheng, KNChiu, BlindTerran, ShiMaRing, lovelock, FreddieLi, FloranceYeh, fanchenggang, gltianwen, goerll, nedchu, curly210102, CuB3y0nd, KraHsu, CarrotDLaw, youshaoXG, bubble9um, Asashishi, Asa0oo0o0o, fanenr, eagleanurag, akshiterate, 52coder, foursevenlove, KorsChen, GaochaoZhu, hopkings2008, yang-le, realwujing, Evilrabbit520, Umer-Jahangir, Turing-1024-Lee, Suremotoo, paoxiaomooo, Chieko-Seren, Allen-Scai, ymmmas, Risuntsy, Richard-Zhang1019, RafaelCaso, qingpeng9802, primexiao, Urbaner3, zhongfq, nidhoggfgg, MwumLi, CreatorMetaSky, martinx, ZnYang2018, hugtyftg, logan-qiu, psychelzh, Keynman, KeiichiKasai, and KawaiiAsh.

The code review work for this book was completed by coderonion, curtishd, Gonglja, gvenusleo, hpstory, justin-tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon and rongyi (in alphabetical order). Thanks to them for the time and effort they put in, it is they who ensure the standardization and unity of code in various languages.

The Traditional Chinese version of this book was reviewed by Shyam-Chen and Dr-XYZ, the English version was reviewed by yuelinxin, K3v123, QiLOL, Phoenix0415, SamJin98, yanedie, RafaelCaso, pengchzn, thomasq0 and magentaqin, and the Japanese edition was reviewed by eltociear. It is because of their continuous contributions that this book can serve a wider readership, and we thank them.

The ePub ebook generation tool for this book was developed by zhongfq. We thank him for his contribution, which provides readers with a more flexible way to read.

During the creation of this book, I received help from many people.

  • Thanks to my mentor at the company, Dr. Li Xi, who encouraged me to \"take action quickly\" during a conversation, strengthening my determination to write this book;
  • Thanks to my girlfriend Bubble as the first reader of this book, who provided many valuable suggestions from the perspective of an algorithm beginner, making this book more suitable for novices to read;
  • Thanks to Tengbao, Qibao, and Feibao for coming up with a creative name for this book, evoking everyone's fond memories of writing their first line of code \"Hello World!\";
  • Thanks to Xiaoquan for providing professional help in intellectual property rights, which played an important role in the improvement of this open-source book;
  • Thanks to Sutong for designing the beautiful cover and logo for this book, and for patiently making revisions multiple times driven by my obsessive-compulsive disorder;
  • Thanks to @squidfunk for the typesetting suggestions, as well as for developing the open-source documentation theme Material-for-MkDocs.

During the writing process, I read many textbooks and articles on data structures and algorithms. These works provided excellent examples for this book and ensured the accuracy and quality of the book's content. I would like to thank all the teachers and predecessors for their outstanding contributions!

This book advocates a learning method that combines hands and brain, and in this regard I was deeply inspired by Dive into Deep Learning. I highly recommend this excellent work to all readers.

Heartfelt thanks to my parents, it is your support and encouragement that has given me the opportunity to do this interesting thing.

","path":["Chapter 0. Preface","0.1   About This Book"],"tags":[]},{"location":"chapter_preface/suggestions/","level":1,"title":"0.2   How to Use This Book","text":"

Tip

For the best reading experience, it is recommended that you read through this section.

","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/suggestions/#021-writing-style-conventions","level":2,"title":"0.2.1   Writing Style Conventions","text":"
  • Titles marked with * are optional sections with relatively difficult content. If you have limited time, you can skip them first.
  • Technical terms will be in bold (in paper and PDF versions) or underlined (in web versions), such as array. It is recommended to memorize them for reading literature.
  • Key content and summary statements will be bolded, and such text deserves special attention.
  • Words and phrases with specific meanings will be marked with \"quotation marks\" to avoid ambiguity.
  • When it comes to nouns that are inconsistent between programming languages, this book uses Python as the standard, for example, using None to represent \"null\".
  • This book partially abandons the comment conventions of programming languages in favor of more compact content layout. Comments are mainly divided into three types: title comments, content comments, and multi-line comments.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
\"\"\"Title comment, used to label functions, classes, test cases, etc.\"\"\"\n\n# Content comment, used to explain code in detail\n\n\"\"\"\nMulti-line\ncomment\n\"\"\"\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n// Multi-line\n// comment\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
### Title comment, used to label functions, classes, test cases, etc. ###\n\n# Content comment, used to explain code in detail\n\n# Multi-line\n# comment\n
","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/suggestions/#022-learning-efficiently-with-animated-illustrations","level":2,"title":"0.2.2   Learning Efficiently with Animated Illustrations","text":"

Compared to text, videos and images have higher information density and structural organization, making them easier to understand. In this book, key and difficult knowledge will mainly be presented in the form of animated illustrations, with text serving as explanation and supplement.

If you find that a section of content provides animated illustrations as shown in Figure 0-2 while reading this book, please focus on the illustrations first, with text as a supplement, and combine the two to understand the content.

Figure 0-2   Example of animated illustrations

","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/suggestions/#023-deepening-understanding-through-code-practice","level":2,"title":"0.2.3   Deepening Understanding Through Code Practice","text":"

The accompanying code for this book is hosted in the GitHub repository. As shown in Figure 0-3, the source code comes with test cases and can be run with one click.

If time permits, it is recommended that you type out the code yourself. If you have limited study time, please at least read through and run all the code.

Compared to reading code, the process of writing code often brings more rewards. Learning by doing is the real learning.

Figure 0-3   Example of running code

The preliminary work for running code is mainly divided into three steps.

Step 1: Install the local programming environment. Please follow the tutorial shown in the appendix for installation. If already installed, you can skip this step.

Step 2: Clone or download the code repository. Visit the GitHub repository. If you have already installed Git, you can clone this repository with the following command:

git clone https://github.com/krahets/hello-algo.git\n

Of course, you can also click the \"Download ZIP\" button at the location shown in Figure 0-4 to directly download the code compressed package, and then extract it locally.

Figure 0-4   Clone repository and download code

Step 3: Run the source code. As shown in Figure 0-5, for code blocks with file names at the top, we can find the corresponding source code files in the codes folder of the repository. The source code files can be run with one click, which will help you save unnecessary debugging time and allow you to focus on learning content.

Figure 0-5   Code blocks and corresponding source code files

In addition to running code locally, the web version also supports visual running of Python code (implemented based on pythontutor). As shown in Figure 0-6, you can click \"Visual Run\" below the code block to expand the view and observe the execution process of the algorithm code; you can also click \"Full Screen View\" for a better viewing experience.

Figure 0-6   Visual running of Python code

","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/suggestions/#024-growing-together-through-questions-and-discussions","level":2,"title":"0.2.4   Growing Together Through Questions and Discussions","text":"

When reading this book, please do not easily skip knowledge points that you have not learned well. Feel free to ask your questions in the comments section, and my friends and I will do our best to answer you, and generally reply within two days.

As shown in Figure 0-7, the web version has a comments section at the bottom of each chapter. I hope you will pay more attention to the content of the comments section. On the one hand, you can learn about the problems that everyone encounters, thus checking for omissions and stimulating deeper thinking. On the other hand, I hope you can generously answer other friends' questions, share your insights, and help others progress.

Figure 0-7   Example of comments section

","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/suggestions/#025-algorithm-learning-roadmap","level":2,"title":"0.2.5   Algorithm Learning Roadmap","text":"

From an overall perspective, we can divide the process of learning data structures and algorithms into three stages.

  1. Stage 1: Algorithm introduction. We need to familiarize ourselves with the characteristics and usage of various data structures, and learn the principles, processes, uses, and efficiency of different algorithms.
  2. Stage 2: Practice algorithm problems. It is recommended to start with popular problems, and accumulate at least 100 problems first, to familiarize yourself with mainstream algorithm problems. When first practicing problems, \"knowledge forgetting\" may be a challenge, but rest assured, this is very normal. We can review problems according to the \"Ebbinghaus forgetting curve\", and usually after 3-5 rounds of repetition, we can firmly remember them. For recommended problem lists and practice plans, please see this GitHub repository.
  3. Stage 3: Building a knowledge system. In terms of learning, we can read algorithm column articles, problem-solving frameworks, and algorithm textbooks to continuously enrich our knowledge system. In terms of practicing problems, we can try advanced problem-solving strategies, such as categorization by topic, one problem multiple solutions, one solution multiple problems, etc. Related problem-solving insights can be found in various communities.

As shown in Figure 0-8, the content of this book mainly covers \"Stage 1\", aiming to help you more efficiently carry out Stage 2 and Stage 3 learning.

Figure 0-8   Algorithm learning roadmap

","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/summary/","level":1,"title":"0.3   Summary","text":"","path":["Chapter 0. Preface","0.3   Summary"],"tags":[]},{"location":"chapter_preface/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • The main audience of this book is algorithm beginners. If you already have a certain foundation, this book can help you systematically review algorithm knowledge, and the source code in the book can also be used as a \"problem-solving toolkit.\"
  • The content of the book mainly includes three parts: complexity analysis, data structures, and algorithms, covering most topics in this field.
  • For algorithm novices, reading an introductory book during the initial learning stage is crucial, as it can help you avoid many detours.
  • The animated illustrations in the book are usually used to introduce key and difficult knowledge. When reading this book, you should pay more attention to these contents.
  • Practice is the best way to learn programming. It is strongly recommended to run the source code and type the code yourself.
  • The web version of this book has a comments section for each chapter, where you are welcome to share your questions and insights at any time.
","path":["Chapter 0. Preface","0.3   Summary"],"tags":[]},{"location":"chapter_reference/","level":1,"title":"References","text":"

[1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition).

[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition).

[3] Robert Sedgewick, et al. Algorithms (4th Edition).

[4] Yan Weimin. Data Structures (C Language Version).

[5] Deng Junhui. Data Structures (C++ Language Version, Third Edition).

[6] Mark Allen Weiss, translated by Chen Yue. Data Structures and Algorithm Analysis in Java (Third Edition).

[7] Cheng Jie. Conversational Data Structures.

[8] Wang Zheng. The Beauty of Data Structures and Algorithms.

[9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition).

[10] Aston Zhang, et al. Dive into Deep Learning.

","path":["References"],"tags":[]},{"location":"chapter_searching/","level":1,"title":"Chapter 10.   Searching","text":"

Abstract

Searching is an adventure into the unknown, where we may need to traverse every corner of the mysterious space, or we may be able to quickly lock onto the target.

In this journey of discovery, each exploration may yield an unexpected answer.

","path":["Chapter 10. Searching","Chapter 10.   Searching"],"tags":[]},{"location":"chapter_searching/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 10.1   Binary Search
  • 10.2   Binary Search Insertion
  • 10.3   Binary Search Edge Cases
  • 10.4   Hash Optimization Strategy
  • 10.5   Search Algorithms Revisited
  • 10.6   Summary
","path":["Chapter 10. Searching","Chapter 10.   Searching"],"tags":[]},{"location":"chapter_searching/binary_search/","level":1,"title":"10.1   Binary Search","text":"

Binary search is an efficient searching algorithm based on the divide-and-conquer strategy. It leverages the orderliness of data to reduce the search range by half in each round until the target element is found or the search interval becomes empty.

Question

Given an array nums of length \\(n\\) with elements arranged in ascending order and no duplicates, search for and return the index of element target in the array. If the array does not contain the element, return \\(-1\\). An example is shown in Figure 10-1.

Figure 10-1   Binary search example data

As shown in Figure 10-2, we first initialize pointers \\(i = 0\\) and \\(j = n - 1\\), pointing to the first and last elements of the array respectively, representing the search interval \\([0, n - 1]\\). Note that square brackets denote a closed interval, which includes the boundary values themselves.

Next, perform the following two steps in a loop:

  1. Calculate the midpoint index \\(m = \\lfloor {(i + j) / 2} \\rfloor\\), where \\(\\lfloor \\: \\rfloor\\) denotes the floor operation.
  2. Compare nums[m] and target, which results in three cases:
    1. When nums[m] < target, it indicates that target is in the interval \\([m + 1, j]\\), so execute \\(i = m + 1\\).
    2. When nums[m] > target, it indicates that target is in the interval \\([i, m - 1]\\), so execute \\(j = m - 1\\).
    3. When nums[m] = target, it indicates that target has been found, so return index \\(m\\).

If the array does not contain the target element, the search interval will eventually shrink to empty. In this case, return \\(-1\\).

<1><2><3><4><5><6><7>

Figure 10-2   Binary search process

It's worth noting that since both \\(i\\) and \\(j\\) are of int type, \\(i + j\\) may exceed the range of the int type. To avoid large number overflow, we typically use the formula \\(m = \\lfloor {i + (j - i) / 2} \\rfloor\\) to calculate the midpoint.

The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search.py
def binary_search(nums: list[int], target: int) -> int:\n    \"\"\"Binary search (closed interval)\"\"\"\n    # Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    i, j = 0, len(nums) - 1\n    # Loop, exit when the search interval is empty (empty when i > j)\n    while i <= j:\n        # In theory, Python numbers can be infinitely large (depending on memory size), no need to consider large number overflow\n        m = (i + j) // 2  # Calculate midpoint index m\n        if nums[m] < target:\n            i = m + 1  # This means target is in the interval [m+1, j]\n        elif nums[m] > target:\n            j = m - 1  # This means target is in the interval [i, m-1]\n        else:\n            return m  # Found the target element, return its index\n    return -1  # Target element not found, return -1\n
binary_search.cpp
/* Binary search (closed interval on both sides) */\nint binarySearch(vector<int> &nums, int target) {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    int i = 0, j = nums.size() - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // This means target is in the interval [m+1, j]\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m-1]\n            j = m - 1;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.java
/* Binary search (closed interval on both sides) */\nint binarySearch(int[] nums, int target) {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    int i = 0, j = nums.length - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) // This means target is in the interval [m+1, j]\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m-1]\n            j = m - 1;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.cs
/* Binary search (closed interval on both sides) */\nint BinarySearch(int[] nums, int target) {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    int i = 0, j = nums.Length - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2;   // Calculate the midpoint index m\n        if (nums[m] < target)      // This means target is in the interval [m+1, j]\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m-1]\n            j = m - 1;\n        else                       // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.go
/* Binary search (closed interval on both sides) */\nfunc binarySearch(nums []int, target int) int {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    i, j := 0, len(nums)-1\n    // Loop, exit when the search interval is empty (empty when i > j)\n    for i <= j {\n        m := i + (j-i)/2      // Calculate the midpoint index m\n        if nums[m] < target { // This means target is in the interval [m+1, j]\n            i = m + 1\n        } else if nums[m] > target { // This means target is in the interval [i, m-1]\n            j = m - 1\n        } else { // Found the target element, return its index\n            return m\n        }\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.swift
/* Binary search (closed interval on both sides) */\nfunc binarySearch(nums: [Int], target: Int) -> Int {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target { // This means target is in the interval [m+1, j]\n            i = m + 1\n        } else if nums[m] > target { // This means target is in the interval [i, m-1]\n            j = m - 1\n        } else { // Found the target element, return its index\n            return m\n        }\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.js
/* Binary search (closed interval on both sides) */\nfunction binarySearch(nums, target) {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    let i = 0,\n        j = nums.length - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        // Calculate midpoint index m, use parseInt() to round down\n        const m = parseInt(i + (j - i) / 2);\n        if (nums[m] < target)\n            // This means target is in the interval [m+1, j]\n            i = m + 1;\n        else if (nums[m] > target)\n            // This means target is in the interval [i, m-1]\n            j = m - 1;\n        else return m; // Found the target element, return its index\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.ts
/* Binary search (closed interval on both sides) */\nfunction binarySearch(nums: number[], target: number): number {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    let i = 0,\n        j = nums.length - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        // Calculate the midpoint index m\n        const m = Math.floor(i + (j - i) / 2);\n        if (nums[m] < target) {\n            // This means target is in the interval [m+1, j]\n            i = m + 1;\n        } else if (nums[m] > target) {\n            // This means target is in the interval [i, m-1]\n            j = m - 1;\n        } else {\n            // Found the target element, return its index\n            return m;\n        }\n    }\n    return -1; // Target element not found, return -1\n}\n
binary_search.dart
/* Binary search (closed interval on both sides) */\nint binarySearch(List<int> nums, int target) {\n  // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n  int i = 0, j = nums.length - 1;\n  // Loop, exit when the search interval is empty (empty when i > j)\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      // This means target is in the interval [m+1, j]\n      i = m + 1;\n    } else if (nums[m] > target) {\n      // This means target is in the interval [i, m-1]\n      j = m - 1;\n    } else {\n      // Found the target element, return its index\n      return m;\n    }\n  }\n  // Target element not found, return -1\n  return -1;\n}\n
binary_search.rs
/* Binary search (closed interval on both sides) */\nfn binary_search(nums: &[i32], target: i32) -> i32 {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    let mut i = 0;\n    let mut j = nums.len() as i32 - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            // This means target is in the interval [m+1, j]\n            i = m + 1;\n        } else if nums[m as usize] > target {\n            // This means target is in the interval [i, m-1]\n            j = m - 1;\n        } else {\n            // Found the target element, return its index\n            return m;\n        }\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.c
/* Binary search (closed interval on both sides) */\nint binarySearch(int *nums, int len, int target) {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    int i = 0, j = len - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // This means target is in the interval [m+1, j]\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m-1]\n            j = m - 1;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.kt
/* Binary search (closed interval on both sides) */\nfun binarySearch(nums: IntArray, target: Int): Int {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    var i = 0\n    var j = nums.size - 1\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) // This means target is in the interval [m+1, j]\n            i = m + 1\n        else if (nums[m] > target) // This means target is in the interval [i, m-1]\n            j = m - 1\n        else  // Found the target element, return its index\n            return m\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.rb
### Binary search (closed interval) ###\ndef binary_search(nums, target)\n  # Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n  i, j = 0, nums.length - 1\n\n  # Loop, exit when the search interval is empty (empty when i > j)\n  while i <= j\n    # In theory, Ruby numbers can be infinitely large (limited by memory), no need to consider overflow\n    m = (i + j) / 2   # Calculate the midpoint index m\n\n    if nums[m] < target\n      i = m + 1 # This means target is in the interval [m+1, j]\n    elsif nums[m] > target\n      j = m - 1 # This means target is in the interval [i, m-1]\n    else\n      return m  # Found the target element, return its index\n    end\n  end\n\n  -1  # Target element not found, return -1\nend\n

Time complexity is \\(O(\\log n)\\): In the binary loop, the interval is reduced by half each round, so the number of loops is \\(\\log_2 n\\).

Space complexity is \\(O(1)\\): Pointers \\(i\\) and \\(j\\) use constant-size space.

","path":["Chapter 10. Searching","10.1   Binary Search"],"tags":[]},{"location":"chapter_searching/binary_search/#1011-interval-representation-methods","level":2,"title":"10.1.1   Interval Representation Methods","text":"

In addition to the closed interval mentioned above, another common interval representation is the \"left-closed right-open\" interval, defined as \\([0, n)\\), meaning the left boundary includes itself while the right boundary does not. Under this representation, the interval \\([i, j)\\) is empty when \\(i = j\\).

We can implement a binary search algorithm with the same functionality based on this representation:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search.py
def binary_search_lcro(nums: list[int], target: int) -> int:\n    \"\"\"Binary search (left-closed right-open interval)\"\"\"\n    # Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    i, j = 0, len(nums)\n    # Loop, exit when the search interval is empty (empty when i = j)\n    while i < j:\n        m = (i + j) // 2  # Calculate midpoint index m\n        if nums[m] < target:\n            i = m + 1  # This means target is in the interval [m+1, j)\n        elif nums[m] > target:\n            j = m  # This means target is in the interval [i, m)\n        else:\n            return m  # Found the target element, return its index\n    return -1  # Target element not found, return -1\n
binary_search.cpp
/* Binary search (left-closed right-open interval) */\nint binarySearchLCRO(vector<int> &nums, int target) {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    int i = 0, j = nums.size();\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // This means target is in the interval [m+1, j)\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m)\n            j = m;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.java
/* Binary search (left-closed right-open interval) */\nint binarySearchLCRO(int[] nums, int target) {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    int i = 0, j = nums.length;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) // This means target is in the interval [m+1, j)\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m)\n            j = m;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.cs
/* Binary search (left-closed right-open interval) */\nint BinarySearchLCRO(int[] nums, int target) {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    int i = 0, j = nums.Length;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2;   // Calculate the midpoint index m\n        if (nums[m] < target)      // This means target is in the interval [m+1, j)\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m)\n            j = m;\n        else                       // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.go
/* Binary search (left-closed right-open interval) */\nfunc binarySearchLCRO(nums []int, target int) int {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    i, j := 0, len(nums)\n    // Loop, exit when the search interval is empty (empty when i = j)\n    for i < j {\n        m := i + (j-i)/2      // Calculate the midpoint index m\n        if nums[m] < target { // This means target is in the interval [m+1, j)\n            i = m + 1\n        } else if nums[m] > target { // This means target is in the interval [i, m)\n            j = m\n        } else { // Found the target element, return its index\n            return m\n        }\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.swift
/* Binary search (left-closed right-open interval) */\nfunc binarySearchLCRO(nums: [Int], target: Int) -> Int {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    var i = nums.startIndex\n    var j = nums.endIndex\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while i < j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target { // This means target is in the interval [m+1, j)\n            i = m + 1\n        } else if nums[m] > target { // This means target is in the interval [i, m)\n            j = m\n        } else { // Found the target element, return its index\n            return m\n        }\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.js
/* Binary search (left-closed right-open interval) */\nfunction binarySearchLCRO(nums, target) {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    let i = 0,\n        j = nums.length;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        // Calculate midpoint index m, use parseInt() to round down\n        const m = parseInt(i + (j - i) / 2);\n        if (nums[m] < target)\n            // This means target is in the interval [m+1, j)\n            i = m + 1;\n        else if (nums[m] > target)\n            // This means target is in the interval [i, m)\n            j = m;\n        // Found the target element, return its index\n        else return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.ts
/* Binary search (left-closed right-open interval) */\nfunction binarySearchLCRO(nums: number[], target: number): number {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    let i = 0,\n        j = nums.length;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        // Calculate the midpoint index m\n        const m = Math.floor(i + (j - i) / 2);\n        if (nums[m] < target) {\n            // This means target is in the interval [m+1, j)\n            i = m + 1;\n        } else if (nums[m] > target) {\n            // This means target is in the interval [i, m)\n            j = m;\n        } else {\n            // Found the target element, return its index\n            return m;\n        }\n    }\n    return -1; // Target element not found, return -1\n}\n
binary_search.dart
/* Binary search (left-closed right-open interval) */\nint binarySearchLCRO(List<int> nums, int target) {\n  // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n  int i = 0, j = nums.length;\n  // Loop, exit when the search interval is empty (empty when i = j)\n  while (i < j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      // This means target is in the interval [m+1, j)\n      i = m + 1;\n    } else if (nums[m] > target) {\n      // This means target is in the interval [i, m)\n      j = m;\n    } else {\n      // Found the target element, return its index\n      return m;\n    }\n  }\n  // Target element not found, return -1\n  return -1;\n}\n
binary_search.rs
/* Binary search (left-closed right-open interval) */\nfn binary_search_lcro(nums: &[i32], target: i32) -> i32 {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    let mut i = 0;\n    let mut j = nums.len() as i32;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while i < j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            // This means target is in the interval [m+1, j)\n            i = m + 1;\n        } else if nums[m as usize] > target {\n            // This means target is in the interval [i, m)\n            j = m;\n        } else {\n            // Found the target element, return its index\n            return m;\n        }\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.c
/* Binary search (left-closed right-open interval) */\nint binarySearchLCRO(int *nums, int len, int target) {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    int i = 0, j = len;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // This means target is in the interval [m+1, j)\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m)\n            j = m;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.kt
/* Binary search (left-closed right-open interval) */\nfun binarySearchLCRO(nums: IntArray, target: Int): Int {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    var i = 0\n    var j = nums.size\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) // This means target is in the interval [m+1, j)\n            i = m + 1\n        else if (nums[m] > target) // This means target is in the interval [i, m)\n            j = m\n        else  // Found the target element, return its index\n            return m\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.rb
### Binary search (left-closed right-open interval) ###\ndef binary_search_lcro(nums, target)\n  # Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n  i, j = 0, nums.length\n\n  # Loop, exit when the search interval is empty (empty when i = j)\n  while i < j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # This means target is in the interval [m+1, j)\n    elsif nums[m] > target\n      j = m - 1 # This means target is in the interval [i, m)\n    else\n      return m  # Found the target element, return its index\n    end\n  end\n\n  -1  # Target element not found, return -1\nend\n

As shown in Figure 10-3, under the two interval representations, the initialization, loop condition, and interval narrowing operations of the binary search algorithm are all different.

Since both the left and right boundaries in the \"closed interval\" representation are defined as closed, the operations to narrow the interval through pointers \\(i\\) and \\(j\\) are also symmetric. This makes it less error-prone, so the \"closed interval\" approach is generally recommended.

Figure 10-3   Two interval definitions

","path":["Chapter 10. Searching","10.1   Binary Search"],"tags":[]},{"location":"chapter_searching/binary_search/#1012-advantages-and-limitations","level":2,"title":"10.1.2   Advantages and Limitations","text":"

Binary search performs well in both time and space aspects.

  • Binary search has high time efficiency. With large data volumes, the logarithmic time complexity has significant advantages. For example, when the data size \\(n = 2^{20}\\), linear search requires \\(2^{20} = 1048576\\) loop rounds, while binary search only needs \\(\\log_2 2^{20} = 20\\) rounds.
  • Binary search requires no extra space. Compared to searching algorithms that require additional space (such as hash-based search), binary search is more space-efficient.

However, binary search is not suitable for all situations, mainly for the following reasons:

  • Binary search is only applicable to sorted data. If the input data is unsorted, sorting specifically to use binary search would be counterproductive, as sorting algorithms typically have a time complexity of \\(O(n \\log n)\\), which is higher than both linear search and binary search. For scenarios with frequent element insertions, maintaining array orderliness requires inserting elements at specific positions with a time complexity of \\(O(n)\\), which is also very expensive.
  • Binary search is only applicable to arrays. Binary search requires jump-style (non-contiguous) element access, and jump-style access has low efficiency in linked lists, making it unsuitable for linked lists or data structures based on linked list implementations.
  • For small data volumes, linear search performs better. In linear search, each round requires only 1 comparison operation; while in binary search, it requires 1 addition, 1 division, 1-3 comparison operations, and 1 addition (subtraction), totaling 4-6 unit operations. Therefore, when the data volume \\(n\\) is small, linear search is actually faster than binary search.
","path":["Chapter 10. Searching","10.1   Binary Search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/","level":1,"title":"10.3   Binary Search Edge Cases","text":"","path":["Chapter 10. Searching","10.3   Binary Search Edge Cases"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1031-finding-the-left-boundary","level":2,"title":"10.3.1   Finding the Left Boundary","text":"

Question

Given a sorted array nums of length \\(n\\) that may contain duplicate elements, return the index of the leftmost element target in the array. If the array does not contain the element, return \\(-1\\).

Recall the method for finding the insertion point with binary search. After the search completes, \\(i\\) points to the leftmost target, so finding the insertion point is essentially finding the index of the leftmost target.

Consider implementing the left boundary search using the insertion point finding function. Note that the array may not contain target, which could result in the following two cases:

  • The insertion point index \\(i\\) is out of bounds.
  • The element nums[i] is not equal to target.

When either of these situations occurs, simply return \\(-1\\). The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_edge.py
def binary_search_left_edge(nums: list[int], target: int) -> int:\n    \"\"\"Binary search for the leftmost target\"\"\"\n    # Equivalent to finding the insertion point of target\n    i = binary_search_insertion(nums, target)\n    # Target not found, return -1\n    if i == len(nums) or nums[i] != target:\n        return -1\n    # Found target, return index i\n    return i\n
binary_search_edge.cpp
/* Binary search for the leftmost target */\nint binarySearchLeftEdge(vector<int> &nums, int target) {\n    // Equivalent to finding the insertion point of target\n    int i = binarySearchInsertion(nums, target);\n    // Target not found, return -1\n    if (i == nums.size() || nums[i] != target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.java
/* Binary search for the leftmost target */\nint binarySearchLeftEdge(int[] nums, int target) {\n    // Equivalent to finding the insertion point of target\n    int i = binary_search_insertion.binarySearchInsertion(nums, target);\n    // Target not found, return -1\n    if (i == nums.length || nums[i] != target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.cs
/* Binary search for the leftmost target */\nint BinarySearchLeftEdge(int[] nums, int target) {\n    // Equivalent to finding the insertion point of target\n    int i = binary_search_insertion.BinarySearchInsertion(nums, target);\n    // Target not found, return -1\n    if (i == nums.Length || nums[i] != target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.go
/* Binary search for the leftmost target */\nfunc binarySearchLeftEdge(nums []int, target int) int {\n    // Equivalent to finding the insertion point of target\n    i := binarySearchInsertion(nums, target)\n    // Target not found, return -1\n    if i == len(nums) || nums[i] != target {\n        return -1\n    }\n    // Found target, return index i\n    return i\n}\n
binary_search_edge.swift
/* Binary search for the leftmost target */\nfunc binarySearchLeftEdge(nums: [Int], target: Int) -> Int {\n    // Equivalent to finding the insertion point of target\n    let i = binarySearchInsertion(nums: nums, target: target)\n    // Target not found, return -1\n    if i == nums.endIndex || nums[i] != target {\n        return -1\n    }\n    // Found target, return index i\n    return i\n}\n
binary_search_edge.js
/* Binary search for the leftmost target */\nfunction binarySearchLeftEdge(nums, target) {\n    // Equivalent to finding the insertion point of target\n    const i = binarySearchInsertion(nums, target);\n    // Target not found, return -1\n    if (i === nums.length || nums[i] !== target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.ts
/* Binary search for the leftmost target */\nfunction binarySearchLeftEdge(nums: Array<number>, target: number): number {\n    // Equivalent to finding the insertion point of target\n    const i = binarySearchInsertion(nums, target);\n    // Target not found, return -1\n    if (i === nums.length || nums[i] !== target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.dart
/* Binary search for the leftmost target */\nint binarySearchLeftEdge(List<int> nums, int target) {\n  // Equivalent to finding the insertion point of target\n  int i = binarySearchInsertion(nums, target);\n  // Target not found, return -1\n  if (i == nums.length || nums[i] != target) {\n    return -1;\n  }\n  // Found target, return index i\n  return i;\n}\n
binary_search_edge.rs
/* Binary search for the leftmost target */\nfn binary_search_left_edge(nums: &[i32], target: i32) -> i32 {\n    // Equivalent to finding the insertion point of target\n    let i = binary_search_insertion(nums, target);\n    // Target not found, return -1\n    if i == nums.len() as i32 || nums[i as usize] != target {\n        return -1;\n    }\n    // Found target, return index i\n    i\n}\n
binary_search_edge.c
/* Binary search for the leftmost target */\nint binarySearchLeftEdge(int *nums, int numSize, int target) {\n    // Equivalent to finding the insertion point of target\n    int i = binarySearchInsertion(nums, numSize, target);\n    // Target not found, return -1\n    if (i == numSize || nums[i] != target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.kt
/* Binary search for the leftmost target */\nfun binarySearchLeftEdge(nums: IntArray, target: Int): Int {\n    // Equivalent to finding the insertion point of target\n    val i = binarySearchInsertion(nums, target)\n    // Target not found, return -1\n    if (i == nums.size || nums[i] != target) {\n        return -1\n    }\n    // Found target, return index i\n    return i\n}\n
binary_search_edge.rb
### Binary search leftmost target ###\ndef binary_search_left_edge(nums, target)\n  # Equivalent to finding the insertion point of target\n  i = binary_search_insertion(nums, target)\n\n  # Target not found, return -1\n  return -1 if i == nums.length || nums[i] != target\n\n  i # Found target, return index i\nend\n
","path":["Chapter 10. Searching","10.3   Binary Search Edge Cases"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1032-finding-the-right-boundary","level":2,"title":"10.3.2   Finding the Right Boundary","text":"

So how do we find the rightmost target? The most direct approach is to modify the code and replace the pointer shrinking operation in the nums[m] == target case. The code is omitted here; interested readers can implement it themselves.

Below we introduce two more clever methods.

","path":["Chapter 10. Searching","10.3   Binary Search Edge Cases"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1-reusing-left-boundary-search","level":3,"title":"1.   Reusing Left Boundary Search","text":"

In fact, we can use the function for finding the leftmost element to find the rightmost element. The specific method is: Convert finding the rightmost target into finding the leftmost target + 1.

As shown in Figure 10-7, after the search completes, pointer \\(i\\) points to the leftmost target + 1 (if it exists), while \\(j\\) points to the rightmost target, so we can simply return \\(j\\).

Figure 10-7   Converting right boundary search to left boundary search

Note that the returned insertion point is \\(i\\), so we need to subtract \\(1\\) from it to obtain \\(j\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_edge.py
def binary_search_right_edge(nums: list[int], target: int) -> int:\n    \"\"\"Binary search for the rightmost target\"\"\"\n    # Convert to finding the leftmost target + 1\n    i = binary_search_insertion(nums, target + 1)\n    # j points to the rightmost target, i points to the first element greater than target\n    j = i - 1\n    # Target not found, return -1\n    if j == -1 or nums[j] != target:\n        return -1\n    # Found target, return index j\n    return j\n
binary_search_edge.cpp
/* Binary search for the rightmost target */\nint binarySearchRightEdge(vector<int> &nums, int target) {\n    // Convert to finding the leftmost target + 1\n    int i = binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    int j = i - 1;\n    // Target not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.java
/* Binary search for the rightmost target */\nint binarySearchRightEdge(int[] nums, int target) {\n    // Convert to finding the leftmost target + 1\n    int i = binary_search_insertion.binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    int j = i - 1;\n    // Target not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.cs
/* Binary search for the rightmost target */\nint BinarySearchRightEdge(int[] nums, int target) {\n    // Convert to finding the leftmost target + 1\n    int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    int j = i - 1;\n    // Target not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.go
/* Binary search for the rightmost target */\nfunc binarySearchRightEdge(nums []int, target int) int {\n    // Convert to finding the leftmost target + 1\n    i := binarySearchInsertion(nums, target+1)\n    // j points to the rightmost target, i points to the first element greater than target\n    j := i - 1\n    // Target not found, return -1\n    if j == -1 || nums[j] != target {\n        return -1\n    }\n    // Found target, return index j\n    return j\n}\n
binary_search_edge.swift
/* Binary search for the rightmost target */\nfunc binarySearchRightEdge(nums: [Int], target: Int) -> Int {\n    // Convert to finding the leftmost target + 1\n    let i = binarySearchInsertion(nums: nums, target: target + 1)\n    // j points to the rightmost target, i points to the first element greater than target\n    let j = i - 1\n    // Target not found, return -1\n    if j == -1 || nums[j] != target {\n        return -1\n    }\n    // Found target, return index j\n    return j\n}\n
binary_search_edge.js
/* Binary search for the rightmost target */\nfunction binarySearchRightEdge(nums, target) {\n    // Convert to finding the leftmost target + 1\n    const i = binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    const j = i - 1;\n    // Target not found, return -1\n    if (j === -1 || nums[j] !== target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.ts
/* Binary search for the rightmost target */\nfunction binarySearchRightEdge(nums: Array<number>, target: number): number {\n    // Convert to finding the leftmost target + 1\n    const i = binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    const j = i - 1;\n    // Target not found, return -1\n    if (j === -1 || nums[j] !== target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.dart
/* Binary search for the rightmost target */\nint binarySearchRightEdge(List<int> nums, int target) {\n  // Convert to finding the leftmost target + 1\n  int i = binarySearchInsertion(nums, target + 1);\n  // j points to the rightmost target, i points to the first element greater than target\n  int j = i - 1;\n  // Target not found, return -1\n  if (j == -1 || nums[j] != target) {\n    return -1;\n  }\n  // Found target, return index j\n  return j;\n}\n
binary_search_edge.rs
/* Binary search for the rightmost target */\nfn binary_search_right_edge(nums: &[i32], target: i32) -> i32 {\n    // Convert to finding the leftmost target + 1\n    let i = binary_search_insertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    let j = i - 1;\n    // Target not found, return -1\n    if j == -1 || nums[j as usize] != target {\n        return -1;\n    }\n    // Found target, return index j\n    j\n}\n
binary_search_edge.c
/* Binary search for the rightmost target */\nint binarySearchRightEdge(int *nums, int numSize, int target) {\n    // Convert to finding the leftmost target + 1\n    int i = binarySearchInsertion(nums, numSize, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    int j = i - 1;\n    // Target not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.kt
/* Binary search for the rightmost target */\nfun binarySearchRightEdge(nums: IntArray, target: Int): Int {\n    // Convert to finding the leftmost target + 1\n    val i = binarySearchInsertion(nums, target + 1)\n    // j points to the rightmost target, i points to the first element greater than target\n    val j = i - 1\n    // Target not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1\n    }\n    // Found target, return index j\n    return j\n}\n
binary_search_edge.rb
### Binary search rightmost target ###\ndef binary_search_right_edge(nums, target)\n  # Convert to finding the leftmost target + 1\n  i = binary_search_insertion(nums, target + 1)\n\n  # j points to the rightmost target, i points to the first element greater than target\n  j = i - 1\n\n  # Target not found, return -1\n  return -1 if j == -1 || nums[j] != target\n\n  j # Found target, return index j\nend\n
","path":["Chapter 10. Searching","10.3   Binary Search Edge Cases"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#2-converting-to-element-search","level":3,"title":"2.   Converting to Element Search","text":"

We know that when the array does not contain target, \\(i\\) and \\(j\\) will eventually point to the first elements greater than and less than target, respectively.

Therefore, as shown in Figure 10-8, we can construct an element that does not exist in the array to find the left and right boundaries.

  • Finding the leftmost target: Can be converted to finding target - 0.5 and returning pointer \\(i\\).
  • Finding the rightmost target: Can be converted to finding target + 0.5 and returning pointer \\(j\\).

Figure 10-8   Converting boundary search to element search

The code is omitted here, but the following two points are worth noting:

  • Since the given array does not contain decimals, we don't need to worry about how to handle equal cases.
  • Because this method introduces decimals, the variable target in the function needs to be changed to a floating-point type (Python does not require this change).
","path":["Chapter 10. Searching","10.3   Binary Search Edge Cases"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/","level":1,"title":"10.2   Binary Search Insertion Point","text":"

Binary search can not only be used to search for target elements but also to solve many variant problems, such as searching for the insertion position of a target element.

","path":["Chapter 10. Searching","10.2   Binary Search Insertion Point"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/#1021-case-without-duplicate-elements","level":2,"title":"10.2.1   Case Without Duplicate Elements","text":"

Question

Given a sorted array nums of length \\(n\\) and an element target, where the array contains no duplicate elements. Insert target into the array nums while maintaining its sorted order. If the array already contains the element target, insert it to its left. Return the index of target in the array after insertion. An example is shown in Figure 10-4.

Figure 10-4   Binary search insertion point example data

If we want to reuse the binary search code from the previous section, we need to answer the following two questions.

Question 1: When the array contains target, is the insertion point index the same as that element's index?

The problem requires inserting target to the left of equal elements, which means the newly inserted target replaces the position of the original target. In other words, when the array contains target, the insertion point index is the index of that target.

Question 2: When the array does not contain target, what is the insertion point index?

Further consider the binary search process: When nums[m] < target, \\(i\\) moves, which means pointer \\(i\\) is approaching elements greater than or equal to target. Similarly, pointer \\(j\\) is always approaching elements less than or equal to target.

Therefore, when the binary search ends, we must have: \\(i\\) points to the first element greater than target, and \\(j\\) points to the first element less than target. It's easy to see that when the array does not contain target, the insertion index is \\(i\\). The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_insertion.py
def binary_search_insertion_simple(nums: list[int], target: int) -> int:\n    \"\"\"Binary search for insertion point (no duplicate elements)\"\"\"\n    i, j = 0, len(nums) - 1  # Initialize closed interval [0, n-1]\n    while i <= j:\n        m = (i + j) // 2  # Calculate midpoint index m\n        if nums[m] < target:\n            i = m + 1  # target is in the interval [m+1, j]\n        elif nums[m] > target:\n            j = m - 1  # target is in the interval [i, m-1]\n        else:\n            return m  # Found target, return insertion point m\n    # Target not found, return insertion point i\n    return i\n
binary_search_insertion.cpp
/* Binary search for insertion point (no duplicate elements) */\nint binarySearchInsertionSimple(vector<int> &nums, int target) {\n    int i = 0, j = nums.size() - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.java
/* Binary search for insertion point (no duplicate elements) */\nint binarySearchInsertionSimple(int[] nums, int target) {\n    int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.cs
/* Binary search for insertion point (no duplicate elements) */\nint BinarySearchInsertionSimple(int[] nums, int target) {\n    int i = 0, j = nums.Length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.go
/* Binary search for insertion point (no duplicate elements) */\nfunc binarySearchInsertionSimple(nums []int, target int) int {\n    // Initialize closed interval [0, n-1]\n    i, j := 0, len(nums)-1\n    for i <= j {\n        // Calculate the midpoint index m\n        m := i + (j-i)/2\n        if nums[m] < target {\n            // target is in the interval [m+1, j]\n            i = m + 1\n        } else if nums[m] > target {\n            // target is in the interval [i, m-1]\n            j = m - 1\n        } else {\n            // Found target, return insertion point m\n            return m\n        }\n    }\n    // Target not found, return insertion point i\n    return i\n}\n
binary_search_insertion.swift
/* Binary search for insertion point (no duplicate elements) */\nfunc binarySearchInsertionSimple(nums: [Int], target: Int) -> Int {\n    // Initialize closed interval [0, n-1]\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target {\n            i = m + 1 // target is in the interval [m+1, j]\n        } else if nums[m] > target {\n            j = m - 1 // target is in the interval [i, m-1]\n        } else {\n            return m // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i\n}\n
binary_search_insertion.js
/* Binary search for insertion point (no duplicate elements) */\nfunction binarySearchInsertionSimple(nums, target) {\n    let i = 0,\n        j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.ts
/* Binary search for insertion point (no duplicate elements) */\nfunction binarySearchInsertionSimple(\n    nums: Array<number>,\n    target: number\n): number {\n    let i = 0,\n        j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.dart
/* Binary search for insertion point (no duplicate elements) */\nint binarySearchInsertionSimple(List<int> nums, int target) {\n  int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1]\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      i = m + 1; // target is in the interval [m+1, j]\n    } else if (nums[m] > target) {\n      j = m - 1; // target is in the interval [i, m-1]\n    } else {\n      return m; // Found target, return insertion point m\n    }\n  }\n  // Target not found, return insertion point i\n  return i;\n}\n
binary_search_insertion.rs
/* Binary search for insertion point (no duplicate elements) */\nfn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 {\n    let (mut i, mut j) = (0, nums.len() as i32 - 1); // Initialize closed interval [0, n-1]\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if nums[m as usize] > target {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m;\n        }\n    }\n    // Target not found, return insertion point i\n    i\n}\n
binary_search_insertion.c
/* Binary search for insertion point (no duplicate elements) */\nint binarySearchInsertionSimple(int *nums, int numSize, int target) {\n    int i = 0, j = numSize - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.kt
/* Binary search for insertion point (no duplicate elements) */\nfun binarySearchInsertionSimple(nums: IntArray, target: Int): Int {\n    var i = 0\n    var j = nums.size - 1 // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1 // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1 // target is in the interval [i, m-1]\n        } else {\n            return m // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i\n}\n
binary_search_insertion.rb
### Binary search insertion point (no duplicates) ###\ndef binary_search_insertion_simple(nums, target)\n  # Initialize closed interval [0, n-1]\n  i, j = 0, nums.length - 1\n\n  while i <= j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # target is in the interval [m+1, j]\n    elsif nums[m] > target\n      j = m - 1 # target is in the interval [i, m-1]\n    else\n      return m  # Found target, return insertion point m\n    end\n  end\n\n  i # Target not found, return insertion point i\nend\n
","path":["Chapter 10. Searching","10.2   Binary Search Insertion Point"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/#1022-case-with-duplicate-elements","level":2,"title":"10.2.2   Case with Duplicate Elements","text":"

Question

Based on the previous problem, assume the array may contain duplicate elements, with everything else remaining the same.

Suppose there are multiple target elements in the array. Ordinary binary search can only return the index of one target, and cannot determine how many target elements are to the left and right of that element.

The problem requires inserting the target element at the leftmost position, so we need to find the index of the leftmost target in the array. Initially, consider implementing this through the steps shown in Figure 10-5:

  1. Perform binary search to obtain the index of any target, denoted as \\(k\\).
  2. Starting from index \\(k\\), perform linear traversal to the left, and return when the leftmost target is found.

Figure 10-5   Linear search for insertion point of duplicate elements

Although this method works, it includes linear search, resulting in a time complexity of \\(O(n)\\). When the array contains many duplicate target elements, this method is very inefficient.

Now consider extending the binary search code. As shown in Figure 10-6, the overall process remains unchanged: calculate the midpoint index \\(m\\) in each round, then compare target with nums[m], divided into the following cases:

  • When nums[m] < target or nums[m] > target, it means target has not been found yet, so use the ordinary binary search interval narrowing operation to make pointers \\(i\\) and \\(j\\) approach target.
  • When nums[m] == target, it means elements less than target are in the interval \\([i, m - 1]\\), so use \\(j = m - 1\\) to narrow the interval, thereby making pointer \\(j\\) approach elements less than target.

After the loop completes, \\(i\\) points to the leftmost target, and \\(j\\) points to the first element less than target, so index \\(i\\) is the insertion point.

<1><2><3><4><5><6><7><8>

Figure 10-6   Steps for binary search insertion point of duplicate elements

Observe the following code: the operations for branches nums[m] > target and nums[m] == target are the same, so the two can be merged.

Even so, we can still keep the conditional branches expanded, as the logic is clearer and more readable.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_insertion.py
def binary_search_insertion(nums: list[int], target: int) -> int:\n    \"\"\"Binary search for insertion point (with duplicate elements)\"\"\"\n    i, j = 0, len(nums) - 1  # Initialize closed interval [0, n-1]\n    while i <= j:\n        m = (i + j) // 2  # Calculate midpoint index m\n        if nums[m] < target:\n            i = m + 1  # target is in the interval [m+1, j]\n        elif nums[m] > target:\n            j = m - 1  # target is in the interval [i, m-1]\n        else:\n            j = m - 1  # The first element less than target is in the interval [i, m-1]\n    # Return insertion point i\n    return i\n
binary_search_insertion.cpp
/* Binary search for insertion point (with duplicate elements) */\nint binarySearchInsertion(vector<int> &nums, int target) {\n    int i = 0, j = nums.size() - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.java
/* Binary search for insertion point (with duplicate elements) */\nint binarySearchInsertion(int[] nums, int target) {\n    int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.cs
/* Binary search for insertion point (with duplicate elements) */\nint BinarySearchInsertion(int[] nums, int target) {\n    int i = 0, j = nums.Length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.go
/* Binary search for insertion point (with duplicate elements) */\nfunc binarySearchInsertion(nums []int, target int) int {\n    // Initialize closed interval [0, n-1]\n    i, j := 0, len(nums)-1\n    for i <= j {\n        // Calculate the midpoint index m\n        m := i + (j-i)/2\n        if nums[m] < target {\n            // target is in the interval [m+1, j]\n            i = m + 1\n        } else if nums[m] > target {\n            // target is in the interval [i, m-1]\n            j = m - 1\n        } else {\n            // The first element less than target is in the interval [i, m-1]\n            j = m - 1\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
binary_search_insertion.swift
/* Binary search for insertion point (with duplicate elements) */\nfunc binarySearchInsertion(nums: [Int], target: Int) -> Int {\n    // Initialize closed interval [0, n-1]\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target {\n            i = m + 1 // target is in the interval [m+1, j]\n        } else if nums[m] > target {\n            j = m - 1 // target is in the interval [i, m-1]\n        } else {\n            j = m - 1 // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
binary_search_insertion.js
/* Binary search for insertion point (with duplicate elements) */\nfunction binarySearchInsertion(nums, target) {\n    let i = 0,\n        j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.ts
/* Binary search for insertion point (with duplicate elements) */\nfunction binarySearchInsertion(nums: Array<number>, target: number): number {\n    let i = 0,\n        j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.dart
/* Binary search for insertion point (with duplicate elements) */\nint binarySearchInsertion(List<int> nums, int target) {\n  int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1]\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      i = m + 1; // target is in the interval [m+1, j]\n    } else if (nums[m] > target) {\n      j = m - 1; // target is in the interval [i, m-1]\n    } else {\n      j = m - 1; // The first element less than target is in the interval [i, m-1]\n    }\n  }\n  // Return insertion point i\n  return i;\n}\n
binary_search_insertion.rs
/* Binary search for insertion point (with duplicate elements) */\npub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 {\n    let (mut i, mut j) = (0, nums.len() as i32 - 1); // Initialize closed interval [0, n-1]\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if nums[m as usize] > target {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    i\n}\n
binary_search_insertion.c
/* Binary search for insertion point (with duplicate elements) */\nint binarySearchInsertion(int *nums, int numSize, int target) {\n    int i = 0, j = numSize - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.kt
/* Binary search for insertion point (with duplicate elements) */\nfun binarySearchInsertion(nums: IntArray, target: Int): Int {\n    var i = 0\n    var j = nums.size - 1 // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1 // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1 // target is in the interval [i, m-1]\n        } else {\n            j = m - 1 // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
binary_search_insertion.rb
### Binary search insertion point (with duplicates) ###\ndef binary_search_insertion(nums, target)\n  # Initialize closed interval [0, n-1]\n  i, j = 0, nums.length - 1\n\n  while i <= j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # target is in the interval [m+1, j]\n    elsif nums[m] > target\n      j = m - 1 # target is in the interval [i, m-1]\n    else\n      j = m - 1 # The first element less than target is in the interval [i, m-1]\n    end\n  end\n\n  i # Return insertion point i\nend\n

Tip

The code in this section all uses the \"closed interval\" approach. Interested readers can implement the \"left-closed right-open\" approach themselves.

Overall, binary search is simply about setting search targets for pointers \\(i\\) and \\(j\\) separately. The target could be a specific element (such as target) or a range of elements (such as elements less than target).

Through continuous binary iterations, both pointers \\(i\\) and \\(j\\) gradually approach their preset targets. Ultimately, they either successfully find the answer or stop after crossing the boundaries.

","path":["Chapter 10. Searching","10.2   Binary Search Insertion Point"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/","level":1,"title":"10.4   Hash Optimization Strategy","text":"

In algorithm problems, we often reduce the time complexity of algorithms by replacing linear search with hash-based search. Let's use an algorithm problem to deepen our understanding.

Question

Given an integer array nums and a target element target, search for two elements in the array whose \"sum\" equals target, and return their array indices. Any solution will do.

","path":["Chapter 10. Searching","10.4   Hash Optimization Strategy"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/#1041-linear-search-trading-time-for-space","level":2,"title":"10.4.1   Linear Search: Trading Time for Space","text":"

Consider directly traversing all possible combinations. As shown in Figure 10-9, we open a two-layer loop and judge in each round whether the sum of two integers equals target. If so, return their indices.

Figure 10-9   Linear search solution for two sum

The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby two_sum.py
def two_sum_brute_force(nums: list[int], target: int) -> list[int]:\n    \"\"\"Method 1: Brute force enumeration\"\"\"\n    # Two nested loops, time complexity is O(n^2)\n    for i in range(len(nums) - 1):\n        for j in range(i + 1, len(nums)):\n            if nums[i] + nums[j] == target:\n                return [i, j]\n    return []\n
two_sum.cpp
/* Method 1: Brute force enumeration */\nvector<int> twoSumBruteForce(vector<int> &nums, int target) {\n    int size = nums.size();\n    // Two nested loops, time complexity is O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return {i, j};\n        }\n    }\n    return {};\n}\n
two_sum.java
/* Method 1: Brute force enumeration */\nint[] twoSumBruteForce(int[] nums, int target) {\n    int size = nums.length;\n    // Two nested loops, time complexity is O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return new int[] { i, j };\n        }\n    }\n    return new int[0];\n}\n
two_sum.cs
/* Method 1: Brute force enumeration */\nint[] TwoSumBruteForce(int[] nums, int target) {\n    int size = nums.Length;\n    // Two nested loops, time complexity is O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return [i, j];\n        }\n    }\n    return [];\n}\n
two_sum.go
/* Method 1: Brute force enumeration */\nfunc twoSumBruteForce(nums []int, target int) []int {\n    size := len(nums)\n    // Two nested loops, time complexity is O(n^2)\n    for i := 0; i < size-1; i++ {\n        for j := i + 1; j < size; j++ {\n            if nums[i]+nums[j] == target {\n                return []int{i, j}\n            }\n        }\n    }\n    return nil\n}\n
two_sum.swift
/* Method 1: Brute force enumeration */\nfunc twoSumBruteForce(nums: [Int], target: Int) -> [Int] {\n    // Two nested loops, time complexity is O(n^2)\n    for i in nums.indices.dropLast() {\n        for j in nums.indices.dropFirst(i + 1) {\n            if nums[i] + nums[j] == target {\n                return [i, j]\n            }\n        }\n    }\n    return [0]\n}\n
two_sum.js
/* Method 1: Brute force enumeration */\nfunction twoSumBruteForce(nums, target) {\n    const n = nums.length;\n    // Two nested loops, time complexity is O(n^2)\n    for (let i = 0; i < n; i++) {\n        for (let j = i + 1; j < n; j++) {\n            if (nums[i] + nums[j] === target) {\n                return [i, j];\n            }\n        }\n    }\n    return [];\n}\n
two_sum.ts
/* Method 1: Brute force enumeration */\nfunction twoSumBruteForce(nums: number[], target: number): number[] {\n    const n = nums.length;\n    // Two nested loops, time complexity is O(n^2)\n    for (let i = 0; i < n; i++) {\n        for (let j = i + 1; j < n; j++) {\n            if (nums[i] + nums[j] === target) {\n                return [i, j];\n            }\n        }\n    }\n    return [];\n}\n
two_sum.dart
/* Method 1: Brute force enumeration */\nList<int> twoSumBruteForce(List<int> nums, int target) {\n  int size = nums.length;\n  // Two nested loops, time complexity is O(n^2)\n  for (var i = 0; i < size - 1; i++) {\n    for (var j = i + 1; j < size; j++) {\n      if (nums[i] + nums[j] == target) return [i, j];\n    }\n  }\n  return [0];\n}\n
two_sum.rs
/* Method 1: Brute force enumeration */\npub fn two_sum_brute_force(nums: &Vec<i32>, target: i32) -> Option<Vec<i32>> {\n    let size = nums.len();\n    // Two nested loops, time complexity is O(n^2)\n    for i in 0..size - 1 {\n        for j in i + 1..size {\n            if nums[i] + nums[j] == target {\n                return Some(vec![i as i32, j as i32]);\n            }\n        }\n    }\n    None\n}\n
two_sum.c
/* Method 1: Brute force enumeration */\nint *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) {\n    for (int i = 0; i < numsSize; ++i) {\n        for (int j = i + 1; j < numsSize; ++j) {\n            if (nums[i] + nums[j] == target) {\n                int *res = malloc(sizeof(int) * 2);\n                res[0] = i, res[1] = j;\n                *returnSize = 2;\n                return res;\n            }\n        }\n    }\n    *returnSize = 0;\n    return NULL;\n}\n
two_sum.kt
/* Method 1: Brute force enumeration */\nfun twoSumBruteForce(nums: IntArray, target: Int): IntArray {\n    val size = nums.size\n    // Two nested loops, time complexity is O(n^2)\n    for (i in 0..<size - 1) {\n        for (j in i + 1..<size) {\n            if (nums[i] + nums[j] == target) return intArrayOf(i, j)\n        }\n    }\n    return IntArray(0)\n}\n
two_sum.rb
### Method 1: Brute force enumeration ###\ndef two_sum_brute_force(nums, target)\n  # Two nested loops, time complexity is O(n^2)\n  for i in 0...(nums.length - 1)\n    for j in (i + 1)...nums.length\n      return [i, j] if nums[i] + nums[j] == target\n    end\n  end\n\n  []\nend\n

This method has a time complexity of \\(O(n^2)\\) and a space complexity of \\(O(1)\\), which is very time-consuming with large data volumes.

","path":["Chapter 10. Searching","10.4   Hash Optimization Strategy"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/#1042-hash-based-search-trading-space-for-time","level":2,"title":"10.4.2   Hash-Based Search: Trading Space for Time","text":"

Consider using a hash table where key-value pairs are array elements and element indices respectively. Loop through the array, performing the steps shown in Figure 10-10 in each round:

  1. Check if the number target - nums[i] is in the hash table. If so, directly return the indices of these two elements.
  2. Add the key-value pair nums[i] and index i to the hash table.
<1><2><3>

Figure 10-10   Hash table solution for two sum

The implementation code is shown below, requiring only a single loop:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby two_sum.py
def two_sum_hash_table(nums: list[int], target: int) -> list[int]:\n    \"\"\"Method 2: Auxiliary hash table\"\"\"\n    # Auxiliary hash table, space complexity is O(n)\n    dic = {}\n    # Single loop, time complexity is O(n)\n    for i in range(len(nums)):\n        if target - nums[i] in dic:\n            return [dic[target - nums[i]], i]\n        dic[nums[i]] = i\n    return []\n
two_sum.cpp
/* Method 2: Auxiliary hash table */\nvector<int> twoSumHashTable(vector<int> &nums, int target) {\n    int size = nums.size();\n    // Auxiliary hash table, space complexity is O(n)\n    unordered_map<int, int> dic;\n    // Single loop, time complexity is O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.find(target - nums[i]) != dic.end()) {\n            return {dic[target - nums[i]], i};\n        }\n        dic.emplace(nums[i], i);\n    }\n    return {};\n}\n
two_sum.java
/* Method 2: Auxiliary hash table */\nint[] twoSumHashTable(int[] nums, int target) {\n    int size = nums.length;\n    // Auxiliary hash table, space complexity is O(n)\n    Map<Integer, Integer> dic = new HashMap<>();\n    // Single loop, time complexity is O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.containsKey(target - nums[i])) {\n            return new int[] { dic.get(target - nums[i]), i };\n        }\n        dic.put(nums[i], i);\n    }\n    return new int[0];\n}\n
two_sum.cs
/* Method 2: Auxiliary hash table */\nint[] TwoSumHashTable(int[] nums, int target) {\n    int size = nums.Length;\n    // Auxiliary hash table, space complexity is O(n)\n    Dictionary<int, int> dic = [];\n    // Single loop, time complexity is O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.ContainsKey(target - nums[i])) {\n            return [dic[target - nums[i]], i];\n        }\n        dic.Add(nums[i], i);\n    }\n    return [];\n}\n
two_sum.go
/* Method 2: Auxiliary hash table */\nfunc twoSumHashTable(nums []int, target int) []int {\n    // Auxiliary hash table, space complexity is O(n)\n    hashTable := map[int]int{}\n    // Single loop, time complexity is O(n)\n    for idx, val := range nums {\n        if preIdx, ok := hashTable[target-val]; ok {\n            return []int{preIdx, idx}\n        }\n        hashTable[val] = idx\n    }\n    return nil\n}\n
two_sum.swift
/* Method 2: Auxiliary hash table */\nfunc twoSumHashTable(nums: [Int], target: Int) -> [Int] {\n    // Auxiliary hash table, space complexity is O(n)\n    var dic: [Int: Int] = [:]\n    // Single loop, time complexity is O(n)\n    for i in nums.indices {\n        if let j = dic[target - nums[i]] {\n            return [j, i]\n        }\n        dic[nums[i]] = i\n    }\n    return [0]\n}\n
two_sum.js
/* Method 2: Auxiliary hash table */\nfunction twoSumHashTable(nums, target) {\n    // Auxiliary hash table, space complexity is O(n)\n    let m = {};\n    // Single loop, time complexity is O(n)\n    for (let i = 0; i < nums.length; i++) {\n        if (m[target - nums[i]] !== undefined) {\n            return [m[target - nums[i]], i];\n        } else {\n            m[nums[i]] = i;\n        }\n    }\n    return [];\n}\n
two_sum.ts
/* Method 2: Auxiliary hash table */\nfunction twoSumHashTable(nums: number[], target: number): number[] {\n    // Auxiliary hash table, space complexity is O(n)\n    let m: Map<number, number> = new Map();\n    // Single loop, time complexity is O(n)\n    for (let i = 0; i < nums.length; i++) {\n        let index = m.get(target - nums[i]);\n        if (index !== undefined) {\n            return [index, i];\n        } else {\n            m.set(nums[i], i);\n        }\n    }\n    return [];\n}\n
two_sum.dart
/* Method 2: Auxiliary hash table */\nList<int> twoSumHashTable(List<int> nums, int target) {\n  int size = nums.length;\n  // Auxiliary hash table, space complexity is O(n)\n  Map<int, int> dic = HashMap();\n  // Single loop, time complexity is O(n)\n  for (var i = 0; i < size; i++) {\n    if (dic.containsKey(target - nums[i])) {\n      return [dic[target - nums[i]]!, i];\n    }\n    dic.putIfAbsent(nums[i], () => i);\n  }\n  return [0];\n}\n
two_sum.rs
/* Method 2: Auxiliary hash table */\npub fn two_sum_hash_table(nums: &Vec<i32>, target: i32) -> Option<Vec<i32>> {\n    // Auxiliary hash table, space complexity is O(n)\n    let mut dic = HashMap::new();\n    // Single loop, time complexity is O(n)\n    for (i, num) in nums.iter().enumerate() {\n        match dic.get(&(target - num)) {\n            Some(v) => return Some(vec![*v as i32, i as i32]),\n            None => dic.insert(num, i as i32),\n        };\n    }\n    None\n}\n
two_sum.c
/* Hash table */\ntypedef struct {\n    int key;\n    int val;\n    UT_hash_handle hh; // Implemented using uthash.h\n} HashTable;\n\n/* Hash table lookup */\nHashTable *find(HashTable *h, int key) {\n    HashTable *tmp;\n    HASH_FIND_INT(h, &key, tmp);\n    return tmp;\n}\n\n/* Hash table element insertion */\nvoid insert(HashTable **h, int key, int val) {\n    HashTable *t = find(*h, key);\n    if (t == NULL) {\n        HashTable *tmp = malloc(sizeof(HashTable));\n        tmp->key = key, tmp->val = val;\n        HASH_ADD_INT(*h, key, tmp);\n    } else {\n        t->val = val;\n    }\n}\n\n/* Method 2: Auxiliary hash table */\nint *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) {\n    HashTable *hashtable = NULL;\n    for (int i = 0; i < numsSize; i++) {\n        HashTable *t = find(hashtable, target - nums[i]);\n        if (t != NULL) {\n            int *res = malloc(sizeof(int) * 2);\n            res[0] = t->val, res[1] = i;\n            *returnSize = 2;\n            return res;\n        }\n        insert(&hashtable, nums[i], i);\n    }\n    *returnSize = 0;\n    return NULL;\n}\n
two_sum.kt
/* Method 2: Auxiliary hash table */\nfun twoSumHashTable(nums: IntArray, target: Int): IntArray {\n    val size = nums.size\n    // Auxiliary hash table, space complexity is O(n)\n    val dic = HashMap<Int, Int>()\n    // Single loop, time complexity is O(n)\n    for (i in 0..<size) {\n        if (dic.containsKey(target - nums[i])) {\n            return intArrayOf(dic[target - nums[i]]!!, i)\n        }\n        dic[nums[i]] = i\n    }\n    return IntArray(0)\n}\n
two_sum.rb
### Method 2: Auxiliary hash table ###\ndef two_sum_hash_table(nums, target)\n  # Auxiliary hash table, space complexity is O(n)\n  dic = {}\n  # Single loop, time complexity is O(n)\n  for i in 0...nums.length\n    return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i])\n\n    dic[nums[i]] = i\n  end\n\n  []\nend\n

This method reduces the time complexity from \\(O(n^2)\\) to \\(O(n)\\) through hash-based search, greatly improving runtime efficiency.

Since an additional hash table needs to be maintained, the space complexity is \\(O(n)\\). Nevertheless, this method achieves a more balanced overall time-space efficiency, making it the optimal solution for this problem.

","path":["Chapter 10. Searching","10.4   Hash Optimization Strategy"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/","level":1,"title":"10.5   Searching Algorithms Revisited","text":"

Searching algorithms are used to search for one or a group of elements that meet specific conditions in data structures (such as arrays, linked lists, trees, or graphs).

Searching algorithms can be divided into the following two categories based on their implementation approach:

  • Locating target elements by traversing the data structure, such as traversing arrays, linked lists, trees, and graphs.
  • Achieving efficient element search by utilizing data organization structure or prior information contained in the data, such as binary search, hash-based search, and binary search tree search.

It's not hard to see that these topics have all been covered in previous chapters, so searching algorithms are not unfamiliar to us. In this section, we will approach from a more systematic perspective and re-examine searching algorithms.

","path":["Chapter 10. Searching","10.5   Searching Algorithms Revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1051-brute-force-search","level":2,"title":"10.5.1   Brute-Force Search","text":"

Brute-force search locates target elements by traversing each element of the data structure.

  • \"Linear search\" is applicable to linear data structures such as arrays and linked lists. It starts from one end of the data structure and accesses elements one by one until the target element is found or the other end is reached without finding the target element.
  • \"Breadth-first search\" and \"depth-first search\" are two traversal strategies for graphs and trees. Breadth-first search starts from the initial node and searches layer by layer, visiting nodes from near to far. Depth-first search starts from the initial node, follows a path to the end, then backtracks and tries other paths until the entire data structure is traversed.

The advantage of brute-force search is that it is simple and has good generality, requiring no data preprocessing or additional data structures.

However, the time complexity of such algorithms is \\(O(n)\\), where \\(n\\) is the number of elements, so performance is poor when dealing with large amounts of data.

","path":["Chapter 10. Searching","10.5   Searching Algorithms Revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1052-adaptive-search","level":2,"title":"10.5.2   Adaptive Search","text":"

Adaptive search utilizes the unique properties of data (such as orderliness) to optimize the search process, thereby locating target elements more efficiently.

  • \"Binary search\" uses the orderliness of data to achieve efficient searching, applicable only to arrays.
  • \"Hash-based search\" uses hash tables to establish key-value pair mappings between search data and target data, thereby achieving query operations.
  • \"Tree search\" in specific tree structures (such as binary search trees), quickly eliminates nodes based on comparing node values to locate target elements.

The advantage of such algorithms is high efficiency, with time complexity reaching \\(O(\\log n)\\) or even \\(O(1)\\).

However, using these algorithms often requires data preprocessing. For example, binary search requires pre-sorting the array, while hash-based search and tree search both require additional data structures, and maintaining these data structures also requires extra time and space overhead.

Tip

Adaptive search algorithms are often called lookup algorithms, mainly used to quickly retrieve target elements in specific data structures.

","path":["Chapter 10. Searching","10.5   Searching Algorithms Revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1053-search-method-selection","level":2,"title":"10.5.3   Search Method Selection","text":"

Given a dataset of size \\(n\\), we can use linear search, binary search, tree search, hash-based search, and other methods to search for the target element. The working principles of each method are shown in Figure 10-11.

Figure 10-11   Multiple search strategies

The operational efficiency and characteristics of the above methods are as follows:

Table 10-1   Comparison of search algorithm efficiency

Linear search Binary search Tree search Hash-based search Search element \\(O(n)\\) \\(O(\\log n)\\) \\(O(\\log n)\\) \\(O(1)\\) Insert element \\(O(1)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\) Delete element \\(O(n)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\) Extra space \\(O(1)\\) \\(O(1)\\) \\(O(n)\\) \\(O(n)\\) Data preprocessing / Sorting \\(O(n \\log n)\\) Tree building \\(O(n \\log n)\\) Hash table building \\(O(n)\\) Data ordered Unordered Ordered Ordered Unordered

The choice of search algorithm also depends on data volume, search performance requirements, data query and update frequency, etc.

Linear search

  • Good generality, requiring no data preprocessing operations. If we only need to query the data once, the data preprocessing time for the other three methods would be longer than linear search.
  • Suitable for small data volumes, where time complexity has less impact on efficiency.
  • Suitable for scenarios with high data update frequency, as this method does not require any additional data maintenance.

Binary search

  • Suitable for large data volumes with stable efficiency performance, worst-case time complexity of \\(O(\\log n)\\).
  • Data volume cannot be too large, as storing arrays requires contiguous memory space.
  • Not suitable for scenarios with frequent data insertion and deletion, as maintaining a sorted array has high overhead.

Hash-based search

  • Suitable for scenarios with high query performance requirements, with an average time complexity of \\(O(1)\\).
  • Not suitable for scenarios requiring ordered data or range searches, as hash tables cannot maintain data orderliness.
  • High dependence on hash functions and hash collision handling strategies, with significant risk of performance degradation.
  • Not suitable for excessively large data volumes, as hash tables require extra space to minimize collisions and thus provide good query performance.

Tree search

  • Suitable for massive data, as tree nodes are stored dispersedly in memory.
  • Suitable for scenarios requiring maintained ordered data or range searches.
  • During continuous node insertion and deletion, binary search trees may become skewed, degrading time complexity to \\(O(n)\\).
  • If using AVL trees or red-black trees, all operations can run stably at \\(O(\\log n)\\) efficiency, but operations to maintain tree balance add extra overhead.
","path":["Chapter 10. Searching","10.5   Searching Algorithms Revisited"],"tags":[]},{"location":"chapter_searching/summary/","level":1,"title":"10.6   Summary","text":"","path":["Chapter 10. Searching","10.6   Summary"],"tags":[]},{"location":"chapter_searching/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Binary search relies on data orderliness and progressively reduces the search interval by half through loops. It requires input data to be sorted and is only applicable to arrays or data structures based on array implementations.
  • Brute-force search locates data by traversing the data structure. Linear search is applicable to arrays and linked lists, while breadth-first search and depth-first search are applicable to graphs and trees. Such algorithms have good generality and require no data preprocessing, but have a relatively high time complexity of \\(O(n)\\).
  • Hash-based search, tree search, and binary search are efficient search methods that can quickly locate target elements in specific data structures. Such algorithms are highly efficient with time complexity reaching \\(O(\\log n)\\) or even \\(O(1)\\), but typically require additional data structures.
  • In practice, we need to analyze factors such as data scale, search performance requirements, and data query and update frequency to choose the appropriate search method.
  • Linear search is suitable for small-scale or frequently updated data; binary search is suitable for large-scale, sorted data; hash-based search is suitable for data with high query efficiency requirements and no need for range queries; tree search is suitable for large-scale dynamic data that needs to maintain order and support range queries.
  • Replacing linear search with hash-based search is a commonly used strategy to optimize runtime, reducing time complexity from \\(O(n)\\) to \\(O(1)\\).
","path":["Chapter 10. Searching","10.6   Summary"],"tags":[]},{"location":"chapter_sorting/","level":1,"title":"Chapter 11.   Sorting","text":"

Abstract

Sorting is like a magic key that transforms chaos into order, enabling us to understand and process data more efficiently.

Whether it's simple ascending order or complex categorized arrangements, sorting demonstrates the harmonious beauty of data.

","path":["Chapter 11. Sorting","Chapter 11.   Sorting"],"tags":[]},{"location":"chapter_sorting/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 11.1   Sorting Algorithms
  • 11.2   Selection Sort
  • 11.3   Bubble Sort
  • 11.4   Insertion Sort
  • 11.5   Quick Sort
  • 11.6   Merge Sort
  • 11.7   Heap Sort
  • 11.8   Bucket Sort
  • 11.9   Counting Sort
  • 11.10   Radix Sort
  • 11.11   Summary
","path":["Chapter 11. Sorting","Chapter 11.   Sorting"],"tags":[]},{"location":"chapter_sorting/bubble_sort/","level":1,"title":"11.3   Bubble Sort","text":"

Bubble sort (bubble sort) achieves sorting by continuously comparing and swapping adjacent elements. This process is like bubbles rising from the bottom to the top, hence the name bubble sort.

As shown in Figure 11-4, the bubbling process can be simulated using element swap operations: starting from the leftmost end of the array and traversing to the right, compare the size of adjacent elements, and if \"left element > right element\", swap them. After completing the traversal, the largest element will be moved to the rightmost end of the array.

<1><2><3><4><5><6><7>

Figure 11-4   Simulating bubble using element swap operation

","path":["Chapter 11. Sorting","11.3   Bubble Sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1131-algorithm-flow","level":2,"title":"11.3.1   Algorithm Flow","text":"

Assume the array has length \\(n\\). The steps of bubble sort are shown in Figure 11-5.

  1. First, perform \"bubbling\" on \\(n\\) elements, swapping the largest element of the array to its correct position.
  2. Next, perform \"bubbling\" on the remaining \\(n - 1\\) elements, swapping the second largest element to its correct position.
  3. And so on. After \\(n - 1\\) rounds of \"bubbling\", the largest \\(n - 1\\) elements have all been swapped to their correct positions.
  4. The only remaining element must be the smallest element, requiring no sorting, so the array sorting is complete.

Figure 11-5   Bubble sort flow

Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bubble_sort.py
def bubble_sort(nums: list[int]):\n    \"\"\"Bubble sort\"\"\"\n    n = len(nums)\n    # Outer loop: unsorted interval is [0, i]\n    for i in range(n - 1, 0, -1):\n        # Inner loop: swap the largest element in the unsorted interval [0, i] to the rightmost end of the interval\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                nums[j], nums[j + 1] = nums[j + 1], nums[j]\n
bubble_sort.cpp
/* Bubble sort */\nvoid bubbleSort(vector<int> &nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                // Using std::swap() function here\n                swap(nums[j], nums[j + 1]);\n            }\n        }\n    }\n}\n
bubble_sort.java
/* Bubble sort */\nvoid bubbleSort(int[] nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
bubble_sort.cs
/* Bubble sort */\nvoid BubbleSort(int[] nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n            }\n        }\n    }\n}\n
bubble_sort.go
/* Bubble sort */\nfunc bubbleSort(nums []int) {\n    // Outer loop: unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                nums[j], nums[j+1] = nums[j+1], nums[j]\n            }\n        }\n    }\n}\n
bubble_sort.swift
/* Bubble sort */\nfunc bubbleSort(nums: inout [Int]) {\n    // Outer loop: unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swapAt(j, j + 1)\n            }\n        }\n    }\n}\n
bubble_sort.js
/* Bubble sort */\nfunction bubbleSort(nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
bubble_sort.ts
/* Bubble sort */\nfunction bubbleSort(nums: number[]): void {\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
bubble_sort.dart
/* Bubble sort */\nvoid bubbleSort(List<int> nums) {\n  // Outer loop: unsorted range is [0, i]\n  for (int i = nums.length - 1; i > 0; i--) {\n    // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for (int j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n      }\n    }\n  }\n}\n
bubble_sort.rs
/* Bubble sort */\nfn bubble_sort(nums: &mut [i32]) {\n    // Outer loop: unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swap(j, j + 1);\n            }\n        }\n    }\n}\n
bubble_sort.c
/* Bubble sort */\nvoid bubbleSort(int nums[], int size) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = size - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                int temp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = temp;\n            }\n        }\n    }\n}\n
bubble_sort.kt
/* Bubble sort */\nfun bubbleSort(nums: IntArray) {\n    // Outer loop: unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n            }\n        }\n    }\n}\n
bubble_sort.rb
### Bubble sort ###\ndef bubble_sort(nums)\n  n = nums.length\n  # Outer loop: unsorted range is [0, i]\n  for i in (n - 1).downto(1)\n    # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        nums[j], nums[j + 1] = nums[j + 1], nums[j]\n      end\n    end\n  end\nend\n
","path":["Chapter 11. Sorting","11.3   Bubble Sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1132-efficiency-optimization","level":2,"title":"11.3.2   Efficiency Optimization","text":"

We notice that if no swap operations are performed during a certain round of \"bubbling\", it means the array has already completed sorting and can directly return the result. Therefore, we can add a flag flag to monitor this situation and return immediately once it occurs.

After optimization, the worst-case time complexity and average time complexity of bubble sort remain \\(O(n^2)\\); but when the input array is completely ordered, the best-case time complexity can reach \\(O(n)\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bubble_sort.py
def bubble_sort_with_flag(nums: list[int]):\n    \"\"\"Bubble sort (flag optimization)\"\"\"\n    n = len(nums)\n    # Outer loop: unsorted interval is [0, i]\n    for i in range(n - 1, 0, -1):\n        flag = False  # Initialize flag\n        # Inner loop: swap the largest element in the unsorted interval [0, i] to the rightmost end of the interval\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                nums[j], nums[j + 1] = nums[j + 1], nums[j]\n                flag = True  # Record element swap\n        if not flag:\n            break  # No elements were swapped in this round of \"bubbling\", exit directly\n
bubble_sort.cpp
/* Bubble sort (flag optimization)*/\nvoid bubbleSortWithFlag(vector<int> &nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        bool flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                // Using std::swap() function here\n                swap(nums[j], nums[j + 1]);\n                flag = true; // Record element swap\n            }\n        }\n        if (!flag)\n            break; // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.java
/* Bubble sort (flag optimization) */\nvoid bubbleSortWithFlag(int[] nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        boolean flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record element swap\n            }\n        }\n        if (!flag)\n            break; // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.cs
/* Bubble sort (flag optimization) */\nvoid BubbleSortWithFlag(int[] nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        bool flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n                flag = true;  // Record element swap\n            }\n        }\n        if (!flag) break;     // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.go
/* Bubble sort (flag optimization) */\nfunc bubbleSortWithFlag(nums []int) {\n    // Outer loop: unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        flag := false // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                nums[j], nums[j+1] = nums[j+1], nums[j]\n                flag = true // Record element swap\n            }\n        }\n        if flag == false { // No elements were swapped in this round of \"bubbling\", exit directly\n            break\n        }\n    }\n}\n
bubble_sort.swift
/* Bubble sort (flag optimization) */\nfunc bubbleSortWithFlag(nums: inout [Int]) {\n    // Outer loop: unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        var flag = false // Initialize flag\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swapAt(j, j + 1)\n                flag = true // Record element swap\n            }\n        }\n        if !flag { // No elements were swapped in this round of \"bubbling\", exit directly\n            break\n        }\n    }\n}\n
bubble_sort.js
/* Bubble sort (flag optimization) */\nfunction bubbleSortWithFlag(nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        let flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record element swap\n            }\n        }\n        if (!flag) break; // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.ts
/* Bubble sort (flag optimization) */\nfunction bubbleSortWithFlag(nums: number[]): void {\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        let flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record element swap\n            }\n        }\n        if (!flag) break; // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.dart
/* Bubble sort (flag optimization) */\nvoid bubbleSortWithFlag(List<int> nums) {\n  // Outer loop: unsorted range is [0, i]\n  for (int i = nums.length - 1; i > 0; i--) {\n    bool flag = false; // Initialize flag\n    // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for (int j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n        flag = true; // Record element swap\n      }\n    }\n    if (!flag) break; // No elements were swapped in this round of \"bubbling\", exit directly\n  }\n}\n
bubble_sort.rs
/* Bubble sort (flag optimization) */\nfn bubble_sort_with_flag(nums: &mut [i32]) {\n    // Outer loop: unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        let mut flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swap(j, j + 1);\n                flag = true; // Record element swap\n            }\n        }\n        if !flag {\n            break; // No elements were swapped in this round of \"bubbling\", exit directly\n        };\n    }\n}\n
bubble_sort.c
/* Bubble sort (flag optimization) */\nvoid bubbleSortWithFlag(int nums[], int size) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = size - 1; i > 0; i--) {\n        bool flag = false;\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                int temp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = temp;\n                flag = true;\n            }\n        }\n        if (!flag)\n            break;\n    }\n}\n
bubble_sort.kt
/* Bubble sort (flag optimization) */\nfun bubbleSortWithFlag(nums: IntArray) {\n    // Outer loop: unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        var flag = false // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n                flag = true // Record element swap\n            }\n        }\n        if (!flag) break // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.rb
### Bubble sort (flag optimization) ###\ndef bubble_sort_with_flag(nums)\n  n = nums.length\n  # Outer loop: unsorted range is [0, i]\n  for i in (n - 1).downto(1)\n    flag = false # Initialize flag\n\n    # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        nums[j], nums[j + 1] = nums[j + 1], nums[j]\n        flag = true # Record element swap\n      end\n    end\n\n    break unless flag # No elements were swapped in this round of \"bubbling\", exit directly\n  end\nend\n
","path":["Chapter 11. Sorting","11.3   Bubble Sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1133-algorithm-characteristics","level":2,"title":"11.3.3   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n^2)\\), adaptive sorting: The array lengths traversed in each round of \"bubbling\" are \\(n - 1\\), \\(n - 2\\), \\(\\dots\\), \\(2\\), \\(1\\), totaling \\((n - 1) n / 2\\). After introducing the flag optimization, the best-case time complexity can reach \\(O(n)\\).
  • Space complexity of \\(O(1)\\), in-place sorting: Pointers \\(i\\) and \\(j\\) use a constant amount of extra space.
  • Stable sorting: Since equal elements are not swapped during \"bubbling\".
","path":["Chapter 11. Sorting","11.3   Bubble Sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/","level":1,"title":"11.8   Bucket Sort","text":"

The several sorting algorithms mentioned earlier all belong to \"comparison-based sorting algorithms\", which achieve sorting by comparing the size of elements. The time complexity of such sorting algorithms cannot exceed \\(O(n \\log n)\\). Next, we will explore several \"non-comparison sorting algorithms\", whose time complexity can reach linear order.

Bucket sort (bucket sort) is a typical application of the divide-and-conquer strategy. It works by setting up buckets with size order, each bucket corresponding to a data range, evenly distributing data to each bucket; then, sorting within each bucket separately; finally, merging all data in the order of the buckets.

","path":["Chapter 11. Sorting","11.8   Bucket Sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1181-algorithm-flow","level":2,"title":"11.8.1   Algorithm Flow","text":"

Consider an array of length \\(n\\), whose elements are floating-point numbers in the range \\([0, 1)\\). The flow of bucket sort is shown in Figure 11-13.

  1. Initialize \\(k\\) buckets and distribute the \\(n\\) elements into the \\(k\\) buckets.
  2. Sort each bucket separately (here we use the built-in sorting function of the programming language).
  3. Merge the results in order from smallest to largest bucket.

Figure 11-13   Bucket sort algorithm flow

The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bucket_sort.py
def bucket_sort(nums: list[float]):\n    \"\"\"Bucket sort\"\"\"\n    # Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    k = len(nums) // 2\n    buckets = [[] for _ in range(k)]\n    # 1. Distribute array elements into various buckets\n    for num in nums:\n        # Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        i = int(num * k)\n        # Add num to bucket i\n        buckets[i].append(num)\n    # 2. Sort each bucket\n    for bucket in buckets:\n        # Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.sort()\n    # 3. Traverse buckets to merge results\n    i = 0\n    for bucket in buckets:\n        for num in bucket:\n            nums[i] = num\n            i += 1\n
bucket_sort.cpp
/* Bucket sort */\nvoid bucketSort(vector<float> &nums) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    int k = nums.size() / 2;\n    vector<vector<float>> buckets(k);\n    // 1. Distribute array elements into various buckets\n    for (float num : nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        int i = num * k;\n        // Add num to bucket bucket_idx\n        buckets[i].push_back(num);\n    }\n    // 2. Sort each bucket\n    for (vector<float> &bucket : buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        sort(bucket.begin(), bucket.end());\n    }\n    // 3. Traverse buckets to merge results\n    int i = 0;\n    for (vector<float> &bucket : buckets) {\n        for (float num : bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
bucket_sort.java
/* Bucket sort */\nvoid bucketSort(float[] nums) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    int k = nums.length / 2;\n    List<List<Float>> buckets = new ArrayList<>();\n    for (int i = 0; i < k; i++) {\n        buckets.add(new ArrayList<>());\n    }\n    // 1. Distribute array elements into various buckets\n    for (float num : nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        int i = (int) (num * k);\n        // Add num to bucket i\n        buckets.get(i).add(num);\n    }\n    // 2. Sort each bucket\n    for (List<Float> bucket : buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        Collections.sort(bucket);\n    }\n    // 3. Traverse buckets to merge results\n    int i = 0;\n    for (List<Float> bucket : buckets) {\n        for (float num : bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
bucket_sort.cs
/* Bucket sort */\nvoid BucketSort(float[] nums) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    int k = nums.Length / 2;\n    List<List<float>> buckets = [];\n    for (int i = 0; i < k; i++) {\n        buckets.Add([]);\n    }\n    // 1. Distribute array elements into various buckets\n    foreach (float num in nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        int i = (int)(num * k);\n        // Add num to bucket i\n        buckets[i].Add(num);\n    }\n    // 2. Sort each bucket\n    foreach (List<float> bucket in buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.Sort();\n    }\n    // 3. Traverse buckets to merge results\n    int j = 0;\n    foreach (List<float> bucket in buckets) {\n        foreach (float num in bucket) {\n            nums[j++] = num;\n        }\n    }\n}\n
bucket_sort.go
/* Bucket sort */\nfunc bucketSort(nums []float64) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    k := len(nums) / 2\n    buckets := make([][]float64, k)\n    for i := 0; i < k; i++ {\n        buckets[i] = make([]float64, 0)\n    }\n    // 1. Distribute array elements into various buckets\n    for _, num := range nums {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        i := int(num * float64(k))\n        // Add num to bucket i\n        buckets[i] = append(buckets[i], num)\n    }\n    // 2. Sort each bucket\n    for i := 0; i < k; i++ {\n        // Use built-in slice sorting function, can also be replaced with other sorting algorithms\n        sort.Float64s(buckets[i])\n    }\n    // 3. Traverse buckets to merge results\n    i := 0\n    for _, bucket := range buckets {\n        for _, num := range bucket {\n            nums[i] = num\n            i++\n        }\n    }\n}\n
bucket_sort.swift
/* Bucket sort */\nfunc bucketSort(nums: inout [Double]) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    let k = nums.count / 2\n    var buckets = (0 ..< k).map { _ in [Double]() }\n    // 1. Distribute array elements into various buckets\n    for num in nums {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        let i = Int(num * Double(k))\n        // Add num to bucket i\n        buckets[i].append(num)\n    }\n    // 2. Sort each bucket\n    for i in buckets.indices {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        buckets[i].sort()\n    }\n    // 3. Traverse buckets to merge results\n    var i = nums.startIndex\n    for bucket in buckets {\n        for num in bucket {\n            nums[i] = num\n            i += 1\n        }\n    }\n}\n
bucket_sort.js
/* Bucket sort */\nfunction bucketSort(nums) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    const k = nums.length / 2;\n    const buckets = [];\n    for (let i = 0; i < k; i++) {\n        buckets.push([]);\n    }\n    // 1. Distribute array elements into various buckets\n    for (const num of nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        const i = Math.floor(num * k);\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for (const bucket of buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.sort((a, b) => a - b);\n    }\n    // 3. Traverse buckets to merge results\n    let i = 0;\n    for (const bucket of buckets) {\n        for (const num of bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
bucket_sort.ts
/* Bucket sort */\nfunction bucketSort(nums: number[]): void {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    const k = nums.length / 2;\n    const buckets: number[][] = [];\n    for (let i = 0; i < k; i++) {\n        buckets.push([]);\n    }\n    // 1. Distribute array elements into various buckets\n    for (const num of nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        const i = Math.floor(num * k);\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for (const bucket of buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.sort((a, b) => a - b);\n    }\n    // 3. Traverse buckets to merge results\n    let i = 0;\n    for (const bucket of buckets) {\n        for (const num of bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
bucket_sort.dart
/* Bucket sort */\nvoid bucketSort(List<double> nums) {\n  // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n  int k = nums.length ~/ 2;\n  List<List<double>> buckets = List.generate(k, (index) => []);\n\n  // 1. Distribute array elements into various buckets\n  for (double _num in nums) {\n    // Input data range is [0, 1), use _num * k to map to index range [0, k-1]\n    int i = (_num * k).toInt();\n    // Add _num to bucket bucket_idx\n    buckets[i].add(_num);\n  }\n  // 2. Sort each bucket\n  for (List<double> bucket in buckets) {\n    bucket.sort();\n  }\n  // 3. Traverse buckets to merge results\n  int i = 0;\n  for (List<double> bucket in buckets) {\n    for (double _num in bucket) {\n      nums[i++] = _num;\n    }\n  }\n}\n
bucket_sort.rs
/* Bucket sort */\nfn bucket_sort(nums: &mut [f64]) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    let k = nums.len() / 2;\n    let mut buckets = vec![vec![]; k];\n    // 1. Distribute array elements into various buckets\n    for &num in nums.iter() {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        let i = (num * k as f64) as usize;\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for bucket in &mut buckets {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.sort_by(|a, b| a.partial_cmp(b).unwrap());\n    }\n    // 3. Traverse buckets to merge results\n    let mut i = 0;\n    for bucket in buckets.iter() {\n        for &num in bucket.iter() {\n            nums[i] = num;\n            i += 1;\n        }\n    }\n}\n
bucket_sort.c
/* Bucket sort */\nvoid bucketSort(float nums[], int n) {\n    int k = n / 2;                                 // Initialize k = n/2 buckets\n    int *sizes = malloc(k * sizeof(int));          // Record each bucket's size\n    float **buckets = malloc(k * sizeof(float *)); // Array of dynamic arrays (buckets)\n    // Pre-allocate sufficient space for each bucket\n    for (int i = 0; i < k; ++i) {\n        buckets[i] = (float *)malloc(n * sizeof(float));\n        sizes[i] = 0;\n    }\n    // 1. Distribute array elements into various buckets\n    for (int i = 0; i < n; ++i) {\n        int idx = (int)(nums[i] * k);\n        buckets[idx][sizes[idx]++] = nums[i];\n    }\n    // 2. Sort each bucket\n    for (int i = 0; i < k; ++i) {\n        qsort(buckets[i], sizes[i], sizeof(float), compare);\n    }\n    // 3. Merge sorted buckets\n    int idx = 0;\n    for (int i = 0; i < k; ++i) {\n        for (int j = 0; j < sizes[i]; ++j) {\n            nums[idx++] = buckets[i][j];\n        }\n        // Free memory\n        free(buckets[i]);\n    }\n}\n
bucket_sort.kt
/* Bucket sort */\nfun bucketSort(nums: FloatArray) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    val k = nums.size / 2\n    val buckets = mutableListOf<MutableList<Float>>()\n    for (i in 0..<k) {\n        buckets.add(mutableListOf())\n    }\n    // 1. Distribute array elements into various buckets\n    for (num in nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        val i = (num * k).toInt()\n        // Add num to bucket i\n        buckets[i].add(num)\n    }\n    // 2. Sort each bucket\n    for (bucket in buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.sort()\n    }\n    // 3. Traverse buckets to merge results\n    var i = 0\n    for (bucket in buckets) {\n        for (num in bucket) {\n            nums[i++] = num\n        }\n    }\n}\n
bucket_sort.rb
### Bucket sort ###\ndef bucket_sort(nums)\n  # Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n  k = nums.length / 2\n  buckets = Array.new(k) { [] }\n\n  # 1. Distribute array elements into various buckets\n  nums.each do |num|\n    # Input data range is [0, 1), use num * k to map to index range [0, k-1]\n    i = (num * k).to_i\n    # Add num to bucket i\n    buckets[i] << num\n  end\n\n  # 2. Sort each bucket\n  buckets.each do |bucket|\n    # Use built-in sorting function, can also replace with other sorting algorithms\n    bucket.sort!\n  end\n\n  # 3. Traverse buckets to merge results\n  i = 0\n  buckets.each do |bucket|\n    bucket.each do |num|\n      nums[i] = num\n      i += 1\n    end\n  end\nend\n
","path":["Chapter 11. Sorting","11.8   Bucket Sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1182-algorithm-characteristics","level":2,"title":"11.8.2   Algorithm Characteristics","text":"

Bucket sort is suitable for processing very large data volumes. For example, if the input data contains 1 million elements and system memory cannot load all the data at once, the data can be divided into 1000 buckets, each bucket sorted separately, and then the results merged.

  • Time complexity of \\(O(n + k)\\): Assuming the elements are evenly distributed among the buckets, then the number of elements in each bucket is \\(\\frac{n}{k}\\). Assuming sorting a single bucket uses \\(O(\\frac{n}{k} \\log\\frac{n}{k})\\) time, then sorting all buckets uses \\(O(n \\log\\frac{n}{k})\\) time. When the number of buckets \\(k\\) is relatively large, the time complexity approaches \\(O(n)\\). Merging results requires traversing all buckets and elements, taking \\(O(n + k)\\) time. In the worst case, all data is distributed into one bucket, and sorting that bucket uses \\(O(n^2)\\) time.
  • Space complexity of \\(O(n + k)\\), non-in-place sorting: Additional space is required for \\(k\\) buckets and a total of \\(n\\) elements.
  • Whether bucket sort is stable depends on whether the algorithm for sorting elements within buckets is stable.
","path":["Chapter 11. Sorting","11.8   Bucket Sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1183-how-to-achieve-even-distribution","level":2,"title":"11.8.3   How to Achieve Even Distribution","text":"

Theoretically, bucket sort can achieve \\(O(n)\\) time complexity. The key is to evenly distribute elements to each bucket, because real data is often not evenly distributed. For example, if we want to evenly distribute all products on Taobao into 10 buckets by price range, there may be very many products below 100 yuan and very few above 1000 yuan. If the price intervals are evenly divided into 10, the difference in the number of products in each bucket will be very large.

To achieve even distribution, we can first set an approximate dividing line to roughly divide the data into 3 buckets. After distribution is complete, continue dividing buckets with more products into 3 buckets until the number of elements in all buckets is roughly equal.

As shown in Figure 11-14, this method essentially creates a recursion tree, with the goal of making the values of leaf nodes as even as possible. Of course, it is not necessary to divide the data into 3 buckets every round; the specific division method can be flexibly chosen according to data characteristics.

Figure 11-14   Recursively dividing buckets

If we know the probability distribution of product prices in advance, we can set the price dividing line for each bucket based on the data probability distribution. It is worth noting that the data distribution does not necessarily need to be specifically calculated, but can also be approximated using a certain probability model based on data characteristics.

As shown in Figure 11-15, we assume that product prices follow a normal distribution, which allows us to reasonably set price intervals to evenly distribute products to each bucket.

Figure 11-15   Dividing buckets based on probability distribution

","path":["Chapter 11. Sorting","11.8   Bucket Sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/","level":1,"title":"11.9   Counting Sort","text":"

Counting sort (counting sort) achieves sorting by counting the number of elements, typically applied to integer arrays.

","path":["Chapter 11. Sorting","11.9   Counting Sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1191-simple-implementation","level":2,"title":"11.9.1   Simple Implementation","text":"

Let's start with a simple example. Given an array nums of length \\(n\\), where the elements are all \"non-negative integers\", the overall flow of counting sort is shown in Figure 11-16.

  1. Traverse the array to find the largest number, denoted as \\(m\\), and then create an auxiliary array counter of length \\(m + 1\\).
  2. Use counter to count the number of occurrences of each number in nums, where counter[num] corresponds to the number of occurrences of the number num. The counting method is simple: just traverse nums (let the current number be num), and increase counter[num] by \\(1\\) in each round.
  3. Since each index of counter is naturally ordered, this is equivalent to all numbers being sorted. Next, we traverse counter and fill in nums in ascending order based on the number of occurrences of each number.

Figure 11-16   Counting sort flow

The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby counting_sort.py
def counting_sort_naive(nums: list[int]):\n    \"\"\"Counting sort\"\"\"\n    # Simple implementation, cannot be used for sorting objects\n    # 1. Count the maximum element m in the array\n    m = 0\n    for num in nums:\n        m = max(m, num)\n    # 2. Count the occurrence of each number\n    # counter[num] represents the occurrence of num\n    counter = [0] * (m + 1)\n    for num in nums:\n        counter[num] += 1\n    # 3. Traverse counter, filling each element back into the original array nums\n    i = 0\n    for num in range(m + 1):\n        for _ in range(counter[num]):\n            nums[i] = num\n            i += 1\n
counting_sort.cpp
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nvoid countingSortNaive(vector<int> &nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int num : nums) {\n        m = max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    vector<int> counter(m + 1, 0);\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
counting_sort.java
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nvoid countingSortNaive(int[] nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int num : nums) {\n        m = Math.max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int[] counter = new int[m + 1];\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
counting_sort.cs
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nvoid CountingSortNaive(int[] nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    foreach (int num in nums) {\n        m = Math.Max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int[] counter = new int[m + 1];\n    foreach (int num in nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
counting_sort.go
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfunc countingSortNaive(nums []int) {\n    // 1. Count the maximum element m in the array\n    m := 0\n    for _, num := range nums {\n        if num > m {\n            m = num\n        }\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    counter := make([]int, m+1)\n    for _, num := range nums {\n        counter[num]++\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    for i, num := 0, 0; num < m+1; num++ {\n        for j := 0; j < counter[num]; j++ {\n            nums[i] = num\n            i++\n        }\n    }\n}\n
counting_sort.swift
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfunc countingSortNaive(nums: inout [Int]) {\n    // 1. Count the maximum element m in the array\n    let m = nums.max()!\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    var counter = Array(repeating: 0, count: m + 1)\n    for num in nums {\n        counter[num] += 1\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    var i = 0\n    for num in 0 ..< m + 1 {\n        for _ in 0 ..< counter[num] {\n            nums[i] = num\n            i += 1\n        }\n    }\n}\n
counting_sort.js
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfunction countingSortNaive(nums) {\n    // 1. Count the maximum element m in the array\n    let m = Math.max(...nums);\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    const counter = new Array(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    let i = 0;\n    for (let num = 0; num < m + 1; num++) {\n        for (let j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
counting_sort.ts
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfunction countingSortNaive(nums: number[]): void {\n    // 1. Count the maximum element m in the array\n    let m: number = Math.max(...nums);\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    const counter: number[] = new Array<number>(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    let i = 0;\n    for (let num = 0; num < m + 1; num++) {\n        for (let j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
counting_sort.dart
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nvoid countingSortNaive(List<int> nums) {\n  // 1. Count the maximum element m in the array\n  int m = 0;\n  for (int _num in nums) {\n    m = max(m, _num);\n  }\n  // 2. Count the occurrence of each number\n  // counter[_num] represents occurrence count of _num\n  List<int> counter = List.filled(m + 1, 0);\n  for (int _num in nums) {\n    counter[_num]++;\n  }\n  // 3. Traverse counter, filling each element back into the original array nums\n  int i = 0;\n  for (int _num = 0; _num < m + 1; _num++) {\n    for (int j = 0; j < counter[_num]; j++, i++) {\n      nums[i] = _num;\n    }\n  }\n}\n
counting_sort.rs
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfn counting_sort_naive(nums: &mut [i32]) {\n    // 1. Count the maximum element m in the array\n    let m = *nums.iter().max().unwrap();\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    let mut counter = vec![0; m as usize + 1];\n    for &num in nums.iter() {\n        counter[num as usize] += 1;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    let mut i = 0;\n    for num in 0..m + 1 {\n        for _ in 0..counter[num as usize] {\n            nums[i] = num;\n            i += 1;\n        }\n    }\n}\n
counting_sort.c
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nvoid countingSortNaive(int nums[], int size) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > m) {\n            m = nums[i];\n        }\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int *counter = calloc(m + 1, sizeof(int));\n    for (int i = 0; i < size; i++) {\n        counter[nums[i]]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n    // 4. Free memory\n    free(counter);\n}\n
counting_sort.kt
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfun countingSortNaive(nums: IntArray) {\n    // 1. Count the maximum element m in the array\n    var m = 0\n    for (num in nums) {\n        m = max(m, num)\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    val counter = IntArray(m + 1)\n    for (num in nums) {\n        counter[num]++\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    var i = 0\n    for (num in 0..<m + 1) {\n        var j = 0\n        while (j < counter[num]) {\n            nums[i] = num\n            j++\n            i++\n        }\n    }\n}\n
counting_sort.rb
### Counting sort ###\ndef counting_sort_naive(nums)\n  # Simple implementation, cannot be used for sorting objects\n  # 1. Count the maximum element m in the array\n  m = 0\n  nums.each { |num| m = [m, num].max }\n  # 2. Count the occurrence of each number\n  # counter[num] represents the occurrence of num\n  counter = Array.new(m + 1, 0)\n  nums.each { |num| counter[num] += 1 }\n  # 3. Traverse counter, filling each element back into the original array nums\n  i = 0\n  for num in 0...(m + 1)\n    (0...counter[num]).each do\n      nums[i] = num\n      i += 1\n    end\n  end\nend\n

Connection between counting sort and bucket sort

From the perspective of bucket sort, we can regard each index of the counting array counter in counting sort as a bucket, and the process of counting quantities as distributing each element to the corresponding bucket. Essentially, counting sort is a special case of bucket sort for integer data.

","path":["Chapter 11. Sorting","11.9   Counting Sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1192-complete-implementation","level":2,"title":"11.9.2   Complete Implementation","text":"

Observant readers may have noticed that if the input data is objects, step 3. above becomes invalid. Suppose the input data is product objects, and we want to sort the products by price (a member variable of the class), but the above algorithm can only give the sorting result of prices.

So how can we obtain the sorting result of the original data? We first calculate the \"prefix sum\" of counter. As the name suggests, the prefix sum at index i, prefix[i], equals the sum of the first i elements of the array:

\\[ \\text{prefix}[i] = \\sum_{j=0}^i \\text{counter[j]} \\]

The prefix sum has a clear meaning: prefix[num] - 1 represents the index of the last occurrence of element num in the result array res. This information is very critical because it tells us where each element should appear in the result array. Next, we traverse each element num of the original array nums in reverse order, performing the following two steps in each iteration.

  1. Fill num into the array res at index prefix[num] - 1.
  2. Decrease the prefix sum prefix[num] by \\(1\\) to get the index for the next placement of num.

After the traversal is complete, the array res contains the sorted result, and finally res is used to overwrite the original array nums. The complete counting sort flow is shown in Figure 11-17.

<1><2><3><4><5><6><7><8>

Figure 11-17   Counting sort steps

The implementation code of counting sort is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby counting_sort.py
def counting_sort(nums: list[int]):\n    \"\"\"Counting sort\"\"\"\n    # Complete implementation, can sort objects and is a stable sort\n    # 1. Count the maximum element m in the array\n    m = max(nums)\n    # 2. Count the occurrence of each number\n    # counter[num] represents the occurrence of num\n    counter = [0] * (m + 1)\n    for num in nums:\n        counter[num] += 1\n    # 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    # counter[num]-1 is the last index where num appears in res\n    for i in range(m):\n        counter[i + 1] += counter[i]\n    # 4. Traverse nums in reverse order, placing each element into the result array res\n    # Initialize the array res to record results\n    n = len(nums)\n    res = [0] * n\n    for i in range(n - 1, -1, -1):\n        num = nums[i]\n        res[counter[num] - 1] = num  # Place num at the corresponding index\n        counter[num] -= 1  # Decrement the prefix sum by 1, getting the next index to place num\n    # Use result array res to overwrite the original array nums\n    for i in range(n):\n        nums[i] = res[i]\n
counting_sort.cpp
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nvoid countingSort(vector<int> &nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int num : nums) {\n        m = max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    vector<int> counter(m + 1, 0);\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    int n = nums.size();\n    vector<int> res(n);\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--;              // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    nums = res;\n}\n
counting_sort.java
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nvoid countingSort(int[] nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int num : nums) {\n        m = Math.max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int[] counter = new int[m + 1];\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    int n = nums.length;\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
counting_sort.cs
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nvoid CountingSort(int[] nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    foreach (int num in nums) {\n        m = Math.Max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int[] counter = new int[m + 1];\n    foreach (int num in nums) {\n        counter[num]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    int n = nums.Length;\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
counting_sort.go
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfunc countingSort(nums []int) {\n    // 1. Count the maximum element m in the array\n    m := 0\n    for _, num := range nums {\n        if num > m {\n            m = num\n        }\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    counter := make([]int, m+1)\n    for _, num := range nums {\n        counter[num]++\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for i := 0; i < m; i++ {\n        counter[i+1] += counter[i]\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    n := len(nums)\n    res := make([]int, n)\n    for i := n - 1; i >= 0; i-- {\n        num := nums[i]\n        // Place num at the corresponding index\n        res[counter[num]-1] = num\n        // Decrement the prefix sum by 1, getting the next index to place num\n        counter[num]--\n    }\n    // Use result array res to overwrite the original array nums\n    copy(nums, res)\n}\n
counting_sort.swift
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfunc countingSort(nums: inout [Int]) {\n    // 1. Count the maximum element m in the array\n    let m = nums.max()!\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    var counter = Array(repeating: 0, count: m + 1)\n    for num in nums {\n        counter[num] += 1\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for i in 0 ..< m {\n        counter[i + 1] += counter[i]\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    var res = Array(repeating: 0, count: nums.count)\n    for i in nums.indices.reversed() {\n        let num = nums[i]\n        res[counter[num] - 1] = num // Place num at the corresponding index\n        counter[num] -= 1 // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for i in nums.indices {\n        nums[i] = res[i]\n    }\n}\n
counting_sort.js
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfunction countingSort(nums) {\n    // 1. Count the maximum element m in the array\n    let m = Math.max(...nums);\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    const counter = new Array(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (let i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    const n = nums.length;\n    const res = new Array(n);\n    for (let i = n - 1; i >= 0; i--) {\n        const num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
counting_sort.ts
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfunction countingSort(nums: number[]): void {\n    // 1. Count the maximum element m in the array\n    let m: number = Math.max(...nums);\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    const counter: number[] = new Array<number>(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (let i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    const n = nums.length;\n    const res: number[] = new Array<number>(n);\n    for (let i = n - 1; i >= 0; i--) {\n        const num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
counting_sort.dart
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nvoid countingSort(List<int> nums) {\n  // 1. Count the maximum element m in the array\n  int m = 0;\n  for (int _num in nums) {\n    m = max(m, _num);\n  }\n  // 2. Count the occurrence of each number\n  // counter[_num] represents occurrence count of _num\n  List<int> counter = List.filled(m + 1, 0);\n  for (int _num in nums) {\n    counter[_num]++;\n  }\n  // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n  // That is, counter[_num]-1 is the last occurrence index of _num in res\n  for (int i = 0; i < m; i++) {\n    counter[i + 1] += counter[i];\n  }\n  // 4. Traverse nums in reverse order, placing each element into the result array res\n  // Initialize the array res to record results\n  int n = nums.length;\n  List<int> res = List.filled(n, 0);\n  for (int i = n - 1; i >= 0; i--) {\n    int _num = nums[i];\n    res[counter[_num] - 1] = _num; // Place _num at corresponding index\n    counter[_num]--; // Decrement prefix sum by 1 to get next placement index for _num\n  }\n  // Use result array res to overwrite the original array nums\n  nums.setAll(0, res);\n}\n
counting_sort.rs
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfn counting_sort(nums: &mut [i32]) {\n    // 1. Count the maximum element m in the array\n    let m = *nums.iter().max().unwrap() as usize;\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    let mut counter = vec![0; m + 1];\n    for &num in nums.iter() {\n        counter[num as usize] += 1;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for i in 0..m {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    let n = nums.len();\n    let mut res = vec![0; n];\n    for i in (0..n).rev() {\n        let num = nums[i];\n        res[counter[num as usize] - 1] = num; // Place num at the corresponding index\n        counter[num as usize] -= 1; // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    nums.copy_from_slice(&res)\n}\n
counting_sort.c
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nvoid countingSort(int nums[], int size) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > m) {\n            m = nums[i];\n        }\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int *counter = calloc(m, sizeof(int));\n    for (int i = 0; i < size; i++) {\n        counter[nums[i]]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    int *res = malloc(sizeof(int) * size);\n    for (int i = size - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--;              // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    memcpy(nums, res, size * sizeof(int));\n    // 5. Free memory\n    free(res);\n    free(counter);\n}\n
counting_sort.kt
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfun countingSort(nums: IntArray) {\n    // 1. Count the maximum element m in the array\n    var m = 0\n    for (num in nums) {\n        m = max(m, num)\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    val counter = IntArray(m + 1)\n    for (num in nums) {\n        counter[num]++\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (i in 0..<m) {\n        counter[i + 1] += counter[i]\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    val n = nums.size\n    val res = IntArray(n)\n    for (i in n - 1 downTo 0) {\n        val num = nums[i]\n        res[counter[num] - 1] = num // Place num at the corresponding index\n        counter[num]-- // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for (i in 0..<n) {\n        nums[i] = res[i]\n    }\n}\n
counting_sort.rb
### Counting sort ###\ndef counting_sort(nums)\n  # Complete implementation, can sort objects and is a stable sort\n  # 1. Count the maximum element m in the array\n  m = nums.max\n  # 2. Count the occurrence of each number\n  # counter[num] represents the occurrence of num\n  counter = Array.new(m + 1, 0)\n  nums.each { |num| counter[num] += 1 }\n  # 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n  # counter[num]-1 is the last index where num appears in res\n  (0...m).each { |i| counter[i + 1] += counter[i] }\n  # 4. Traverse nums in reverse, fill elements into result array res\n  # Initialize the array res to record results\n  n = nums.length\n  res = Array.new(n, 0)\n  (n - 1).downto(0).each do |i|\n    num = nums[i]\n    res[counter[num] - 1] = num # Place num at the corresponding index\n    counter[num] -= 1 # Decrement the prefix sum by 1, getting the next index to place num\n  end\n  # Use result array res to overwrite the original array nums\n  (0...n).each { |i| nums[i] = res[i] }\nend\n
","path":["Chapter 11. Sorting","11.9   Counting Sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1193-algorithm-characteristics","level":2,"title":"11.9.3   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n + m)\\), non-adaptive sorting: Involves traversing nums and traversing counter, both using linear time. Generally, \\(n \\gg m\\), and time complexity tends toward \\(O(n)\\).
  • Space complexity of \\(O(n + m)\\), non-in-place sorting: Uses arrays res and counter of lengths \\(n\\) and \\(m\\) respectively.
  • Stable sorting: Since elements are filled into res in a \"right-to-left\" order, traversing nums in reverse can avoid changing the relative positions of equal elements, thereby achieving stable sorting. In fact, traversing nums in forward order can also yield correct sorting results, but the result would be unstable.
","path":["Chapter 11. Sorting","11.9   Counting Sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1194-limitations","level":2,"title":"11.9.4   Limitations","text":"

By this point, you might think counting sort is very clever, as it can achieve efficient sorting just by counting quantities. However, the prerequisites for using counting sort are relatively strict.

Counting sort is only suitable for non-negative integers. If you want to apply it to other types of data, you need to ensure that the data can be converted to non-negative integers without changing the relative size relationships between elements. For example, for an integer array containing negative numbers, you can first add a constant to all numbers to convert them all to positive numbers, and then convert them back after sorting is complete.

Counting sort is suitable for situations where the data volume is large but the data range is small. For example, in the above example, \\(m\\) cannot be too large, otherwise it will occupy too much space. And when \\(n \\ll m\\), counting sort uses \\(O(m)\\) time, which may be slower than \\(O(n \\log n)\\) sorting algorithms.

","path":["Chapter 11. Sorting","11.9   Counting Sort"],"tags":[]},{"location":"chapter_sorting/heap_sort/","level":1,"title":"11.7   Heap Sort","text":"

Tip

Before reading this section, please ensure you have completed the \"Heap\" chapter.

Heap sort (heap sort) is an efficient sorting algorithm based on the heap data structure. We can use the \"build heap operation\" and \"element out-heap operation\" that we have already learned to implement heap sort.

  1. Input the array and build a min-heap, at which point the smallest element is at the heap top.
  2. Continuously perform the out-heap operation, record the out-heap elements in sequence, and an ascending sorted sequence can be obtained.

Although the above method is feasible, it requires an additional array to save the popped elements, which is quite wasteful of space. In practice, we usually use a more elegant implementation method.

","path":["Chapter 11. Sorting","11.7   Heap Sort"],"tags":[]},{"location":"chapter_sorting/heap_sort/#1171-algorithm-flow","level":2,"title":"11.7.1   Algorithm Flow","text":"

Assume the array length is \\(n\\). The flow of heap sort is shown in Figure 11-12.

  1. Input the array and build a max-heap. After completion, the largest element is at the heap top.
  2. Swap the heap top element (first element) with the heap bottom element (last element). After the swap is complete, reduce the heap length by \\(1\\) and increase the count of sorted elements by \\(1\\).
  3. Starting from the heap top element, perform top-to-bottom heapify operation (sift down). After heapify is complete, the heap property is restored.
  4. Loop through steps 2. and 3. After looping \\(n - 1\\) rounds, the array sorting can be completed.

Tip

In fact, the element out-heap operation also includes steps 2. and 3., with just an additional step to pop the element.

<1><2><3><4><5><6><7><8><9><10><11><12>

Figure 11-12   Heap sort steps

In the code implementation, we use the same top-to-bottom heapify function sift_down() from the \"Heap\" chapter. It is worth noting that since the heap length will decrease as the largest element is extracted, we need to add a length parameter \\(n\\) to the sift_down() function to specify the current effective length of the heap. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby heap_sort.py
def sift_down(nums: list[int], n: int, i: int):\n    \"\"\"Heap length is n, start heapifying node i, from top to bottom\"\"\"\n    while True:\n        # Determine the largest node among i, l, r, noted as ma\n        l = 2 * i + 1\n        r = 2 * i + 2\n        ma = i\n        if l < n and nums[l] > nums[ma]:\n            ma = l\n        if r < n and nums[r] > nums[ma]:\n            ma = r\n        # If node i is the largest or indices l, r are out of bounds, no further heapification needed, break\n        if ma == i:\n            break\n        # Swap two nodes\n        nums[i], nums[ma] = nums[ma], nums[i]\n        # Loop downwards heapification\n        i = ma\n\ndef heap_sort(nums: list[int]):\n    \"\"\"Heap sort\"\"\"\n    # Build heap operation: heapify all nodes except leaves\n    for i in range(len(nums) // 2 - 1, -1, -1):\n        sift_down(nums, len(nums), i)\n    # Extract the largest element from the heap and repeat for n-1 rounds\n    for i in range(len(nums) - 1, 0, -1):\n        # Swap the root node with the rightmost leaf node (swap the first element with the last element)\n        nums[0], nums[i] = nums[i], nums[0]\n        # Start heapifying the root node, from top to bottom\n        sift_down(nums, i, 0)\n
heap_sort.cpp
/* Heap length is n, start heapifying node i, from top to bottom */\nvoid siftDown(vector<int> &nums, int n, int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Swap two nodes\n        if (ma == i) {\n            break;\n        }\n        // Swap two nodes\n        swap(nums[i], nums[ma]);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(vector<int> &nums) {\n    // Build heap operation: heapify all nodes except leaves\n    for (int i = nums.size() / 2 - 1; i >= 0; --i) {\n        siftDown(nums, nums.size(), i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (int i = nums.size() - 1; i > 0; --i) {\n        // Delete node\n        swap(nums[0], nums[i]);\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0);\n    }\n}\n
heap_sort.java
/* Heap length is n, start heapifying node i, from top to bottom */\nvoid siftDown(int[] nums, int n, int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Swap two nodes\n        if (ma == i)\n            break;\n        // Swap two nodes\n        int temp = nums[i];\n        nums[i] = nums[ma];\n        nums[ma] = temp;\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(int[] nums) {\n    // Build heap operation: heapify all nodes except leaves\n    for (int i = nums.length / 2 - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Delete node\n        int tmp = nums[0];\n        nums[0] = nums[i];\n        nums[i] = tmp;\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0);\n    }\n}\n
heap_sort.cs
/* Heap length is n, start heapifying node i, from top to bottom */\nvoid SiftDown(int[] nums, int n, int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Swap two nodes\n        if (ma == i)\n            break;\n        // Swap two nodes\n        (nums[ma], nums[i]) = (nums[i], nums[ma]);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid HeapSort(int[] nums) {\n    // Build heap operation: heapify all nodes except leaves\n    for (int i = nums.Length / 2 - 1; i >= 0; i--) {\n        SiftDown(nums, nums.Length, i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Delete node\n        (nums[i], nums[0]) = (nums[0], nums[i]);\n        // Start heapifying the root node, from top to bottom\n        SiftDown(nums, i, 0);\n    }\n}\n
heap_sort.go
/* Heap length is n, start heapifying node i, from top to bottom */\nfunc siftDown(nums *[]int, n, i int) {\n    for true {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        l := 2*i + 1\n        r := 2*i + 2\n        ma := i\n        if l < n && (*nums)[l] > (*nums)[ma] {\n            ma = l\n        }\n        if r < n && (*nums)[r] > (*nums)[ma] {\n            ma = r\n        }\n        // Swap two nodes\n        if ma == i {\n            break\n        }\n        // Swap two nodes\n        (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i]\n        // Loop downwards heapification\n        i = ma\n    }\n}\n\n/* Heap sort */\nfunc heapSort(nums *[]int) {\n    // Build heap operation: heapify all nodes except leaves\n    for i := len(*nums)/2 - 1; i >= 0; i-- {\n        siftDown(nums, len(*nums), i)\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for i := len(*nums) - 1; i > 0; i-- {\n        // Delete node\n        (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0]\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0)\n    }\n}\n
heap_sort.swift
/* Heap length is n, start heapifying node i, from top to bottom */\nfunc siftDown(nums: inout [Int], n: Int, i: Int) {\n    var i = i\n    while true {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let l = 2 * i + 1\n        let r = 2 * i + 2\n        var ma = i\n        if l < n, nums[l] > nums[ma] {\n            ma = l\n        }\n        if r < n, nums[r] > nums[ma] {\n            ma = r\n        }\n        // Swap two nodes\n        if ma == i {\n            break\n        }\n        // Swap two nodes\n        nums.swapAt(i, ma)\n        // Loop downwards heapification\n        i = ma\n    }\n}\n\n/* Heap sort */\nfunc heapSort(nums: inout [Int]) {\n    // Build heap operation: heapify all nodes except leaves\n    for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) {\n        siftDown(nums: &nums, n: nums.count, i: i)\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for i in nums.indices.dropFirst().reversed() {\n        // Delete node\n        nums.swapAt(0, i)\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums: &nums, n: i, i: 0)\n    }\n}\n
heap_sort.js
/* Heap length is n, start heapifying node i, from top to bottom */\nfunction siftDown(nums, n, i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let ma = i;\n        if (l < n && nums[l] > nums[ma]) {\n            ma = l;\n        }\n        if (r < n && nums[r] > nums[ma]) {\n            ma = r;\n        }\n        // Swap two nodes\n        if (ma === i) {\n            break;\n        }\n        // Swap two nodes\n        [nums[i], nums[ma]] = [nums[ma], nums[i]];\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfunction heapSort(nums) {\n    // Build heap operation: heapify all nodes except leaves\n    for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Delete node\n        [nums[0], nums[i]] = [nums[i], nums[0]];\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0);\n    }\n}\n
heap_sort.ts
/* Heap length is n, start heapifying node i, from top to bottom */\nfunction siftDown(nums: number[], n: number, i: number): void {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let ma = i;\n        if (l < n && nums[l] > nums[ma]) {\n            ma = l;\n        }\n        if (r < n && nums[r] > nums[ma]) {\n            ma = r;\n        }\n        // Swap two nodes\n        if (ma === i) {\n            break;\n        }\n        // Swap two nodes\n        [nums[i], nums[ma]] = [nums[ma], nums[i]];\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfunction heapSort(nums: number[]): void {\n    // Build heap operation: heapify all nodes except leaves\n    for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Delete node\n        [nums[0], nums[i]] = [nums[i], nums[0]];\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0);\n    }\n}\n
heap_sort.dart
/* Heap length is n, start heapifying node i, from top to bottom */\nvoid siftDown(List<int> nums, int n, int i) {\n  while (true) {\n    // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n    int l = 2 * i + 1;\n    int r = 2 * i + 2;\n    int ma = i;\n    if (l < n && nums[l] > nums[ma]) ma = l;\n    if (r < n && nums[r] > nums[ma]) ma = r;\n    // Swap two nodes\n    if (ma == i) break;\n    // Swap two nodes\n    int temp = nums[i];\n    nums[i] = nums[ma];\n    nums[ma] = temp;\n    // Loop downwards heapification\n    i = ma;\n  }\n}\n\n/* Heap sort */\nvoid heapSort(List<int> nums) {\n  // Build heap operation: heapify all nodes except leaves\n  for (int i = nums.length ~/ 2 - 1; i >= 0; i--) {\n    siftDown(nums, nums.length, i);\n  }\n  // Extract the largest element from the heap and repeat for n-1 rounds\n  for (int i = nums.length - 1; i > 0; i--) {\n    // Delete node\n    int tmp = nums[0];\n    nums[0] = nums[i];\n    nums[i] = tmp;\n    // Start heapifying the root node, from top to bottom\n    siftDown(nums, i, 0);\n  }\n}\n
heap_sort.rs
/* Heap length is n, start heapifying node i, from top to bottom */\nfn sift_down(nums: &mut [i32], n: usize, mut i: usize) {\n    loop {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let mut ma = i;\n        if l < n && nums[l] > nums[ma] {\n            ma = l;\n        }\n        if r < n && nums[r] > nums[ma] {\n            ma = r;\n        }\n        // Swap two nodes\n        if ma == i {\n            break;\n        }\n        // Swap two nodes\n        nums.swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfn heap_sort(nums: &mut [i32]) {\n    // Build heap operation: heapify all nodes except leaves\n    for i in (0..nums.len() / 2).rev() {\n        sift_down(nums, nums.len(), i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for i in (1..nums.len()).rev() {\n        // Delete node\n        nums.swap(0, i);\n        // Start heapifying the root node, from top to bottom\n        sift_down(nums, i, 0);\n    }\n}\n
heap_sort.c
/* Heap length is n, start heapifying node i, from top to bottom */\nvoid siftDown(int nums[], int n, int i) {\n    while (1) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Swap two nodes\n        if (ma == i) {\n            break;\n        }\n        // Swap two nodes\n        int temp = nums[i];\n        nums[i] = nums[ma];\n        nums[ma] = temp;\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(int nums[], int n) {\n    // Build heap operation: heapify all nodes except leaves\n    for (int i = n / 2 - 1; i >= 0; --i) {\n        siftDown(nums, n, i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (int i = n - 1; i > 0; --i) {\n        // Delete node\n        int tmp = nums[0];\n        nums[0] = nums[i];\n        nums[i] = tmp;\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0);\n    }\n}\n
heap_sort.kt
/* Heap length is n, start heapifying node i, from top to bottom */\nfun siftDown(nums: IntArray, n: Int, li: Int) {\n    var i = li\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        val l = 2 * i + 1\n        val r = 2 * i + 2\n        var ma = i\n        if (l < n && nums[l] > nums[ma]) \n            ma = l\n        if (r < n && nums[r] > nums[ma]) \n            ma = r\n        // Swap two nodes\n        if (ma == i) \n            break\n        // Swap two nodes\n        val temp = nums[i]\n        nums[i] = nums[ma]\n        nums[ma] = temp\n        // Loop downwards heapification\n        i = ma\n    }\n}\n\n/* Heap sort */\nfun heapSort(nums: IntArray) {\n    // Build heap operation: heapify all nodes except leaves\n    for (i in nums.size / 2 - 1 downTo 0) {\n        siftDown(nums, nums.size, i)\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (i in nums.size - 1 downTo 1) {\n        // Delete node\n        val temp = nums[0]\n        nums[0] = nums[i]\n        nums[i] = temp\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0)\n    }\n}\n
heap_sort.rb
### Heap length is n, heapify from node i, top to bottom ###\ndef sift_down(nums, n, i)\n  while true\n    # If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n    l = 2 * i + 1\n    r = 2 * i + 2\n    ma = i\n    ma = l if l < n && nums[l] > nums[ma]\n    ma = r if r < n && nums[r] > nums[ma]\n    # Swap two nodes\n    break if ma == i\n    # Swap two nodes\n    nums[i], nums[ma] = nums[ma], nums[i]\n    # Loop downwards heapification\n    i = ma\n  end\nend\n\n### Heap sort ###\ndef heap_sort(nums)\n  # Build heap operation: heapify all nodes except leaves\n  (nums.length / 2 - 1).downto(0) do |i|\n    sift_down(nums, nums.length, i)\n  end\n  # Extract the largest element from the heap and repeat for n-1 rounds\n  (nums.length - 1).downto(1) do |i|\n    # Delete node\n    nums[0], nums[i] = nums[i], nums[0]\n    # Start heapifying the root node, from top to bottom\n    sift_down(nums, i, 0)\n  end\nend\n
","path":["Chapter 11. Sorting","11.7   Heap Sort"],"tags":[]},{"location":"chapter_sorting/heap_sort/#1172-algorithm-characteristics","level":2,"title":"11.7.2   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n \\log n)\\), non-adaptive sorting: The build heap operation uses \\(O(n)\\) time. Extracting the largest element from the heap has a time complexity of \\(O(\\log n)\\), looping a total of \\(n - 1\\) rounds.
  • Space complexity of \\(O(1)\\), in-place sorting: A few pointer variables use \\(O(1)\\) space. Element swapping and heapify operations are both performed on the original array.
  • Non-stable sorting: When swapping the heap top element and heap bottom element, the relative positions of equal elements may change.
","path":["Chapter 11. Sorting","11.7   Heap Sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/","level":1,"title":"11.4   Insertion Sort","text":"

Insertion sort (insertion sort) is a simple sorting algorithm that works very similarly to the process of manually organizing a deck of cards.

Specifically, we select a base element from the unsorted interval, compare the element with elements in the sorted interval to its left one by one, and insert the element into the correct position.

Figure 11-6 shows the operation flow of inserting an element into the array. Let the base element be base. We need to move all elements from the target index to base one position to the right, and then assign base to the target index.

Figure 11-6   Single insertion operation

","path":["Chapter 11. Sorting","11.4   Insertion Sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1141-algorithm-flow","level":2,"title":"11.4.1   Algorithm Flow","text":"

The overall flow of insertion sort is shown in Figure 11-7.

  1. Initially, the first element of the array has completed sorting.
  2. Select the second element of the array as base, and after inserting it into the correct position, the first 2 elements of the array are sorted.
  3. Select the third element as base, and after inserting it into the correct position, the first 3 elements of the array are sorted.
  4. And so on. In the last round, select the last element as base, and after inserting it into the correct position, all elements are sorted.

Figure 11-7   Insertion sort flow

Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby insertion_sort.py
def insertion_sort(nums: list[int]):\n    \"\"\"Insertion sort\"\"\"\n    # Outer loop: sorted interval is [0, i-1]\n    for i in range(1, len(nums)):\n        base = nums[i]\n        j = i - 1\n        # Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while j >= 0 and nums[j] > base:\n            nums[j + 1] = nums[j]  # Move nums[j] to the right by one position\n            j -= 1\n        nums[j + 1] = base  # Assign base to the correct position\n
insertion_sort.cpp
/* Insertion sort */\nvoid insertionSort(vector<int> &nums) {\n    // Outer loop: sorted interval is [0, i-1]\n    for (int i = 1; i < nums.size(); i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n            j--;\n        }\n        nums[j + 1] = base; // Assign base to the correct position\n    }\n}\n
insertion_sort.java
/* Insertion sort */\nvoid insertionSort(int[] nums) {\n    // Outer loop: sorted interval is [0, i-1]\n    for (int i = 1; i < nums.length; i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n            j--;\n        }\n        nums[j + 1] = base;        // Assign base to the correct position\n    }\n}\n
insertion_sort.cs
/* Insertion sort */\nvoid InsertionSort(int[] nums) {\n    // Outer loop: sorted interval is [0, i-1]\n    for (int i = 1; i < nums.Length; i++) {\n        int bas = nums[i], j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > bas) {\n            nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n            j--;\n        }\n        nums[j + 1] = bas;         // Assign base to the correct position\n    }\n}\n
insertion_sort.go
/* Insertion sort */\nfunc insertionSort(nums []int) {\n    // Outer loop: sorted interval is [0, i-1]\n    for i := 1; i < len(nums); i++ {\n        base := nums[i]\n        j := i - 1\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        for j >= 0 && nums[j] > base {\n            nums[j+1] = nums[j] // Move nums[j] to the right by one position\n            j--\n        }\n        nums[j+1] = base // Assign base to the correct position\n    }\n}\n
insertion_sort.swift
/* Insertion sort */\nfunc insertionSort(nums: inout [Int]) {\n    // Outer loop: sorted interval is [0, i-1]\n    for i in nums.indices.dropFirst() {\n        let base = nums[i]\n        var j = i - 1\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while j >= 0, nums[j] > base {\n            nums[j + 1] = nums[j] // Move nums[j] to the right by one position\n            j -= 1\n        }\n        nums[j + 1] = base // Assign base to the correct position\n    }\n}\n
insertion_sort.js
/* Insertion sort */\nfunction insertionSort(nums) {\n    // Outer loop: sorted interval is [0, i-1]\n    for (let i = 1; i < nums.length; i++) {\n        let base = nums[i],\n            j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n            j--;\n        }\n        nums[j + 1] = base; // Assign base to the correct position\n    }\n}\n
insertion_sort.ts
/* Insertion sort */\nfunction insertionSort(nums: number[]): void {\n    // Outer loop: sorted interval is [0, i-1]\n    for (let i = 1; i < nums.length; i++) {\n        const base = nums[i];\n        let j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n            j--;\n        }\n        nums[j + 1] = base; // Assign base to the correct position\n    }\n}\n
insertion_sort.dart
/* Insertion sort */\nvoid insertionSort(List<int> nums) {\n  // Outer loop: sorted interval is [0, i-1]\n  for (int i = 1; i < nums.length; i++) {\n    int base = nums[i], j = i - 1;\n    // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n    while (j >= 0 && nums[j] > base) {\n      nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n      j--;\n    }\n    nums[j + 1] = base; // Assign base to the correct position\n  }\n}\n
insertion_sort.rs
/* Insertion sort */\nfn insertion_sort(nums: &mut [i32]) {\n    // Outer loop: sorted interval is [0, i-1]\n    for i in 1..nums.len() {\n        let (base, mut j) = (nums[i], (i - 1) as i32);\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while j >= 0 && nums[j as usize] > base {\n            nums[(j + 1) as usize] = nums[j as usize]; // Move nums[j] to the right by one position\n            j -= 1;\n        }\n        nums[(j + 1) as usize] = base; // Assign base to the correct position\n    }\n}\n
insertion_sort.c
/* Insertion sort */\nvoid insertionSort(int nums[], int size) {\n    // Outer loop: sorted interval is [0, i-1]\n    for (int i = 1; i < size; i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            // Move nums[j] to the right by one position\n            nums[j + 1] = nums[j];\n            j--;\n        }\n        // Assign base to the correct position\n        nums[j + 1] = base;\n    }\n}\n
insertion_sort.kt
/* Insertion sort */\nfun insertionSort(nums: IntArray) {\n    // Outer loop: sorted elements are 1, 2, ..., n\n    for (i in nums.indices) {\n        val base = nums[i]\n        var j = i - 1\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j] // Move nums[j] to the right by one position\n            j--\n        }\n        nums[j + 1] = base        // Assign base to the correct position\n    }\n}\n
insertion_sort.rb
### Insertion sort ###\ndef insertion_sort(nums)\n  n = nums.length\n  # Outer loop: sorted interval is [0, i-1]\n  for i in 1...n\n    base = nums[i]\n    j = i - 1\n    # Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n    while j >= 0 && nums[j] > base\n      nums[j + 1] = nums[j] # Move nums[j] to the right by one position\n      j -= 1\n    end\n    nums[j + 1] = base # Assign base to the correct position\n  end\nend\n
","path":["Chapter 11. Sorting","11.4   Insertion Sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1142-algorithm-characteristics","level":2,"title":"11.4.2   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n^2)\\), adaptive sorting: In the worst case, each insertion operation requires loops of \\(n - 1\\), \\(n-2\\), \\(\\dots\\), \\(2\\), \\(1\\), summing to \\((n - 1) n / 2\\), so the time complexity is \\(O(n^2)\\). When encountering ordered data, the insertion operation will terminate early. When the input array is completely ordered, insertion sort achieves the best-case time complexity of \\(O(n)\\).
  • Space complexity of \\(O(1)\\), in-place sorting: Pointers \\(i\\) and \\(j\\) use a constant amount of extra space.
  • Stable sorting: During the insertion operation process, we insert elements to the right of equal elements, without changing their order.
","path":["Chapter 11. Sorting","11.4   Insertion Sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1143-advantages-of-insertion-sort","level":2,"title":"11.4.3   Advantages of Insertion Sort","text":"

The time complexity of insertion sort is \\(O(n^2)\\), while the time complexity of quick sort, which we will learn about next, is \\(O(n \\log n)\\). Although insertion sort has a higher time complexity, insertion sort is usually faster for smaller data volumes.

This conclusion is similar to the applicable situations of linear search and binary search. Algorithms like quick sort with \\(O(n \\log n)\\) complexity are sorting algorithms based on divide-and-conquer strategy and often contain more unit computation operations. When the data volume is small, \\(n^2\\) and \\(n \\log n\\) are numerically close, and complexity does not dominate; the number of unit operations per round plays a decisive role.

In fact, the built-in sorting functions in many programming languages (such as Java) adopt insertion sort. The general approach is: for long arrays, use sorting algorithms based on divide-and-conquer strategy, such as quick sort; for short arrays, directly use insertion sort.

Although bubble sort, selection sort, and insertion sort all have a time complexity of \\(O(n^2)\\), in actual situations, insertion sort is used significantly more frequently than bubble sort and selection sort, mainly for the following reasons.

  • Bubble sort is based on element swapping, requiring the use of a temporary variable, involving 3 unit operations; insertion sort is based on element assignment, requiring only 1 unit operation. Therefore, the computational overhead of bubble sort is usually higher than that of insertion sort.
  • Selection sort has a time complexity of \\(O(n^2)\\) in any case. If given a set of partially ordered data, insertion sort is usually more efficient than selection sort.
  • Selection sort is unstable and cannot be applied to multi-level sorting.
","path":["Chapter 11. Sorting","11.4   Insertion Sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/","level":1,"title":"11.6   Merge Sort","text":"

Merge sort (merge sort) is a sorting algorithm based on the divide-and-conquer strategy, which includes the \"divide\" and \"merge\" phases shown in Figure 11-10.

  1. Divide phase: Recursively split the array from the midpoint, transforming the sorting problem of a long array into the sorting problems of shorter arrays.
  2. Merge phase: When the sub-array length is 1, terminate the division and start merging, continuously merging two shorter sorted arrays into one longer sorted array until the process is complete.

Figure 11-10   Divide and merge phases of merge sort

","path":["Chapter 11. Sorting","11.6   Merge Sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1161-algorithm-flow","level":2,"title":"11.6.1   Algorithm Flow","text":"

As shown in Figure 11-11, the \"divide phase\" recursively splits the array from the midpoint into two sub-arrays from top to bottom.

  1. Calculate the array midpoint mid, recursively divide the left sub-array (interval [left, mid]) and right sub-array (interval [mid + 1, right]).
  2. Recursively execute step 1. until the sub-array interval length is 1, then terminate.

The \"merge phase\" merges the left sub-array and right sub-array into a sorted array from bottom to top. Note that merging starts from sub-arrays of length 1, and each sub-array in the merge phase is sorted.

<1><2><3><4><5><6><7><8><9><10>

Figure 11-11   Merge sort steps

It can be observed that the recursive order of merge sort is consistent with the post-order traversal of a binary tree.

  • Post-order traversal: First recursively traverse the left subtree, then recursively traverse the right subtree, and finally process the root node.
  • Merge sort: First recursively process the left sub-array, then recursively process the right sub-array, and finally perform the merge.

The implementation of merge sort is shown in the code below. Note that the interval to be merged in nums is [left, right], while the corresponding interval in tmp is [0, right - left].

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby merge_sort.py
def merge(nums: list[int], left: int, mid: int, right: int):\n    \"\"\"Merge left subarray and right subarray\"\"\"\n    # Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    # Create a temporary array tmp to store the merged results\n    tmp = [0] * (right - left + 1)\n    # Initialize the start indices of the left and right subarrays\n    i, j, k = left, mid + 1, 0\n    # While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while i <= mid and j <= right:\n        if nums[i] <= nums[j]:\n            tmp[k] = nums[i]\n            i += 1\n        else:\n            tmp[k] = nums[j]\n            j += 1\n        k += 1\n    # Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid:\n        tmp[k] = nums[i]\n        i += 1\n        k += 1\n    while j <= right:\n        tmp[k] = nums[j]\n        j += 1\n        k += 1\n    # Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for k in range(0, len(tmp)):\n        nums[left + k] = tmp[k]\n\ndef merge_sort(nums: list[int], left: int, right: int):\n    \"\"\"Merge sort\"\"\"\n    # Termination condition\n    if left >= right:\n        return  # Terminate recursion when subarray length is 1\n    # Divide and conquer stage\n    mid = (left + right) // 2  # Calculate midpoint\n    merge_sort(nums, left, mid)  # Recursively process the left subarray\n    merge_sort(nums, mid + 1, right)  # Recursively process the right subarray\n    # Merge stage\n    merge(nums, left, mid, right)\n
merge_sort.cpp
/* Merge left subarray and right subarray */\nvoid merge(vector<int> &nums, int left, int mid, int right) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    vector<int> tmp(right - left + 1);\n    // Initialize the start indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmp.size(); k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid mergeSort(vector<int> &nums, int left, int right) {\n    // Termination condition\n    if (left >= right)\n        return; // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    int mid = left + (right - left) / 2;    // Calculate midpoint\n    mergeSort(nums, left, mid);      // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.java
/* Merge left subarray and right subarray */\nvoid merge(int[] nums, int left, int mid, int right) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    int[] tmp = new int[right - left + 1];\n    // Initialize the start indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid mergeSort(int[] nums, int left, int right) {\n    // Termination condition\n    if (left >= right)\n        return; // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    int mid = left + (right - left) / 2; // Calculate midpoint\n    mergeSort(nums, left, mid); // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.cs
/* Merge left subarray and right subarray */\nvoid Merge(int[] nums, int left, int mid, int right) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    int[] tmp = new int[right - left + 1];\n    // Initialize the start indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmp.Length; ++k) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid MergeSort(int[] nums, int left, int right) {\n    // Termination condition\n    if (left >= right) return;       // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    int mid = left + (right - left) / 2;    // Calculate midpoint\n    MergeSort(nums, left, mid);      // Recursively process the left subarray\n    MergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    Merge(nums, left, mid, right);\n}\n
merge_sort.go
/* Merge left subarray and right subarray */\nfunc merge(nums []int, left, mid, right int) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    tmp := make([]int, right-left+1)\n    // Initialize the start indices of the left and right subarrays\n    i, j, k := left, mid+1, 0\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    for i <= mid && j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i]\n            i++\n        } else {\n            tmp[k] = nums[j]\n            j++\n        }\n        k++\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    for i <= mid {\n        tmp[k] = nums[i]\n        i++\n        k++\n    }\n    for j <= right {\n        tmp[k] = nums[j]\n        j++\n        k++\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for k := 0; k < len(tmp); k++ {\n        nums[left+k] = tmp[k]\n    }\n}\n\n/* Merge sort */\nfunc mergeSort(nums []int, left, right int) {\n    // Termination condition\n    if left >= right {\n        return\n    }\n    // Divide and conquer stage\n    mid := left + (right - left) / 2\n    mergeSort(nums, left, mid)\n    mergeSort(nums, mid+1, right)\n    // Merge stage\n    merge(nums, left, mid, right)\n}\n
merge_sort.swift
/* Merge left subarray and right subarray */\nfunc merge(nums: inout [Int], left: Int, mid: Int, right: Int) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    var tmp = Array(repeating: 0, count: right - left + 1)\n    // Initialize the start indices of the left and right subarrays\n    var i = left, j = mid + 1, k = 0\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while i <= mid, j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i]\n            i += 1\n        } else {\n            tmp[k] = nums[j]\n            j += 1\n        }\n        k += 1\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid {\n        tmp[k] = nums[i]\n        i += 1\n        k += 1\n    }\n    while j <= right {\n        tmp[k] = nums[j]\n        j += 1\n        k += 1\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for k in tmp.indices {\n        nums[left + k] = tmp[k]\n    }\n}\n\n/* Merge sort */\nfunc mergeSort(nums: inout [Int], left: Int, right: Int) {\n    // Termination condition\n    if left >= right { // Terminate recursion when subarray length is 1\n        return\n    }\n    // Divide and conquer stage\n    let mid = left + (right - left) / 2 // Calculate midpoint\n    mergeSort(nums: &nums, left: left, right: mid) // Recursively process the left subarray\n    mergeSort(nums: &nums, left: mid + 1, right: right) // Recursively process the right subarray\n    // Merge stage\n    merge(nums: &nums, left: left, mid: mid, right: right)\n}\n
merge_sort.js
/* Merge left subarray and right subarray */\nfunction merge(nums, left, mid, right) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    const tmp = new Array(right - left + 1);\n    // Initialize the start indices of the left and right subarrays\n    let i = left,\n        j = mid + 1,\n        k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfunction mergeSort(nums, left, right) {\n    // Termination condition\n    if (left >= right) return; // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    let mid = Math.floor(left + (right - left) / 2); // Calculate midpoint\n    mergeSort(nums, left, mid); // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.ts
/* Merge left subarray and right subarray */\nfunction merge(nums: number[], left: number, mid: number, right: number): void {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    const tmp = new Array(right - left + 1);\n    // Initialize the start indices of the left and right subarrays\n    let i = left,\n        j = mid + 1,\n        k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfunction mergeSort(nums: number[], left: number, right: number): void {\n    // Termination condition\n    if (left >= right) return; // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    let mid = Math.floor(left + (right - left) / 2); // Calculate midpoint\n    mergeSort(nums, left, mid); // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.dart
/* Merge left subarray and right subarray */\nvoid merge(List<int> nums, int left, int mid, int right) {\n  // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n  // Create a temporary array tmp to store the merged results\n  List<int> tmp = List.filled(right - left + 1, 0);\n  // Initialize the start indices of the left and right subarrays\n  int i = left, j = mid + 1, k = 0;\n  // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n  while (i <= mid && j <= right) {\n    if (nums[i] <= nums[j])\n      tmp[k++] = nums[i++];\n    else\n      tmp[k++] = nums[j++];\n  }\n  // Copy the remaining elements of the left and right subarrays into the temporary array\n  while (i <= mid) {\n    tmp[k++] = nums[i++];\n  }\n  while (j <= right) {\n    tmp[k++] = nums[j++];\n  }\n  // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n  for (k = 0; k < tmp.length; k++) {\n    nums[left + k] = tmp[k];\n  }\n}\n\n/* Merge sort */\nvoid mergeSort(List<int> nums, int left, int right) {\n  // Termination condition\n  if (left >= right) return; // Terminate recursion when subarray length is 1\n  // Divide and conquer stage\n  int mid = left + (right - left) ~/ 2; // Calculate midpoint\n  mergeSort(nums, left, mid); // Recursively process the left subarray\n  mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n  // Merge stage\n  merge(nums, left, mid, right);\n}\n
merge_sort.rs
/* Merge left subarray and right subarray */\nfn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    let tmp_size = right - left + 1;\n    let mut tmp = vec![0; tmp_size];\n    // Initialize the start indices of the left and right subarrays\n    let (mut i, mut j, mut k) = (left, mid + 1, 0);\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while i <= mid && j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i];\n            i += 1;\n        } else {\n            tmp[k] = nums[j];\n            j += 1;\n        }\n        k += 1;\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid {\n        tmp[k] = nums[i];\n        k += 1;\n        i += 1;\n    }\n    while j <= right {\n        tmp[k] = nums[j];\n        k += 1;\n        j += 1;\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for k in 0..tmp_size {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfn merge_sort(nums: &mut [i32], left: usize, right: usize) {\n    // Termination condition\n    if left >= right {\n        return; // Terminate recursion when subarray length is 1\n    }\n\n    // Divide and conquer stage\n    let mid = left + (right - left) / 2; // Calculate midpoint\n    merge_sort(nums, left, mid); // Recursively process the left subarray\n    merge_sort(nums, mid + 1, right); // Recursively process the right subarray\n\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.c
/* Merge left subarray and right subarray */\nvoid merge(int *nums, int left, int mid, int right) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    int tmpSize = right - left + 1;\n    int *tmp = (int *)malloc(tmpSize * sizeof(int));\n    // Initialize the start indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmpSize; ++k) {\n        nums[left + k] = tmp[k];\n    }\n    // Free memory\n    free(tmp);\n}\n\n/* Merge sort */\nvoid mergeSort(int *nums, int left, int right) {\n    // Termination condition\n    if (left >= right)\n        return; // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    int mid = left + (right - left) / 2;    // Calculate midpoint\n    mergeSort(nums, left, mid);      // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.kt
/* Merge left subarray and right subarray */\nfun merge(nums: IntArray, left: Int, mid: Int, right: Int) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    val tmp = IntArray(right - left + 1)\n    // Initialize the start indices of the left and right subarrays\n    var i = left\n    var j = mid + 1\n    var k = 0\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++]\n        else\n            tmp[k++] = nums[j++]\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++]\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++]\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (l in tmp.indices) {\n        nums[left + l] = tmp[l]\n    }\n}\n\n/* Merge sort */\nfun mergeSort(nums: IntArray, left: Int, right: Int) {\n    // Termination condition\n    if (left >= right) return  // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    val mid = left + (right - left) / 2 // Calculate midpoint\n    mergeSort(nums, left, mid) // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right) // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right)\n}\n
merge_sort.rb
### Merge left and right subarrays ###\ndef merge(nums, left, mid, right)\n  # Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n  # Create temporary array tmp to store merged result\n  tmp = Array.new(right - left + 1, 0)\n  # Initialize the start indices of the left and right subarrays\n  i, j, k = left, mid + 1, 0\n  # While both subarrays still have elements, compare and copy the smaller element into the temporary array\n  while i <= mid && j <= right\n    if nums[i] <= nums[j]\n      tmp[k] = nums[i]\n      i += 1\n    else\n      tmp[k] = nums[j]\n      j += 1\n    end\n    k += 1\n  end\n  # Copy the remaining elements of the left and right subarrays into the temporary array\n  while i <= mid\n    tmp[k] = nums[i]\n    i += 1\n    k += 1\n  end\n  while j <= right\n    tmp[k] = nums[j]\n    j += 1\n    k += 1\n  end\n  # Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n  (0...tmp.length).each do |k|\n    nums[left + k] = tmp[k]\n  end\nend\n\n### Merge sort ###\ndef merge_sort(nums, left, right)\n  # Termination condition\n  # Terminate recursion when subarray length is 1\n  return if left >= right\n  # Divide and conquer stage\n  mid = left + (right - left) / 2 # Calculate midpoint\n  merge_sort(nums, left, mid) # Recursively process the left subarray\n  merge_sort(nums, mid + 1, right) # Recursively process the right subarray\n  # Merge stage\n  merge(nums, left, mid, right)\nend\n
","path":["Chapter 11. Sorting","11.6   Merge Sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1162-algorithm-characteristics","level":2,"title":"11.6.2   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n \\log n)\\), non-adaptive sorting: The division produces a recursion tree of height \\(\\log n\\), and the total number of merge operations at each level is \\(n\\), so the overall time complexity is \\(O(n \\log n)\\).
  • Space complexity of \\(O(n)\\), non-in-place sorting: The recursion depth is \\(\\log n\\), using \\(O(\\log n)\\) size of stack frame space. The merge operation requires the aid of an auxiliary array, using \\(O(n)\\) size of additional space.
  • Stable sorting: In the merge process, the order of equal elements remains unchanged.
","path":["Chapter 11. Sorting","11.6   Merge Sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1163-linked-list-sorting","level":2,"title":"11.6.3   Linked List Sorting","text":"

For linked lists, merge sort has significant advantages over other sorting algorithms, and can optimize the space complexity of linked list sorting tasks to \\(O(1)\\).

  • Divide phase: \"Iteration\" can be used instead of \"recursion\" to implement linked list division work, thus saving the stack frame space used by recursion.
  • Merge phase: In linked lists, node insertion and deletion operations can be achieved by just changing references (pointers), so there is no need to create additional linked lists during the merge phase (merging two short ordered linked lists into one long ordered linked list).

The specific implementation details are quite complex, and interested readers can consult related materials for learning.

","path":["Chapter 11. Sorting","11.6   Merge Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/","level":1,"title":"11.5   Quick Sort","text":"

Quick sort (quick sort) is a sorting algorithm based on the divide-and-conquer strategy, which operates efficiently and is widely applied.

The core operation of quick sort is \"sentinel partitioning\", which aims to: select a certain element in the array as the \"pivot\", move all elements smaller than the pivot to its left, and move elements larger than the pivot to its right. Specifically, the flow of sentinel partitioning is shown in Figure 11-8.

  1. Select the leftmost element of the array as the pivot, and initialize two pointers i and j pointing to the two ends of the array.
  2. Set up a loop in which i (j) is used in each round to find the first element larger (smaller) than the pivot, and then swap these two elements.
  3. Loop through step 2. until i and j meet, and finally swap the pivot to the boundary line of the two sub-arrays.
<1><2><3><4><5><6><7><8><9>

Figure 11-8   Sentinel partitioning steps

After sentinel partitioning is complete, the original array is divided into three parts: left sub-array, pivot, right sub-array, satisfying \"any element in left sub-array \\(\\leq\\) pivot \\(\\leq\\) any element in right sub-array\". Therefore, we next only need to sort these two sub-arrays.

Divide-and-conquer strategy of quick sort

The essence of sentinel partitioning is to simplify the sorting problem of a longer array into the sorting problems of two shorter arrays.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
def partition(self, nums: list[int], left: int, right: int) -> int:\n    \"\"\"Sentinel partition\"\"\"\n    # Use nums[left] as the pivot\n    i, j = left, right\n    while i < j:\n        while i < j and nums[j] >= nums[left]:\n            j -= 1  # Search from right to left for the first element smaller than the pivot\n        while i < j and nums[i] <= nums[left]:\n            i += 1  # Search from left to right for the first element greater than the pivot\n        # Swap elements\n        nums[i], nums[j] = nums[j], nums[i]\n    # Swap the pivot to the boundary between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i  # Return the index of the pivot\n
quick_sort.cpp
/* Sentinel partition */\nint partition(vector<int> &nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;                // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;                // Search from left to right for the first element greater than the pivot\n        swap(nums[i], nums[j]); // Swap these two elements\n    }\n    swap(nums[i], nums[left]);  // Swap the pivot to the boundary between the two subarrays\n    return i;                   // Return the index of the pivot\n}\n
quick_sort.java
/* Swap elements */\nvoid swap(int[] nums, int i, int j) {\n    int tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Sentinel partition */\nint partition(int[] nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Search from left to right for the first element greater than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left);  // Swap the pivot to the boundary between the two subarrays\n    return i;             // Return the index of the pivot\n}\n
quick_sort.cs
/* Swap elements */\nvoid Swap(int[] nums, int i, int j) {\n    (nums[j], nums[i]) = (nums[i], nums[j]);\n}\n\n/* Sentinel partition */\nint Partition(int[] nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Search from left to right for the first element greater than the pivot\n        Swap(nums, i, j); // Swap these two elements\n    }\n    Swap(nums, i, left);  // Swap the pivot to the boundary between the two subarrays\n    return i;             // Return the index of the pivot\n}\n
quick_sort.go
/* Sentinel partition */\nfunc (q *quickSort) partition(nums []int, left, right int) int {\n    // Use nums[left] as the pivot\n    i, j := left, right\n    for i < j {\n        for i < j && nums[j] >= nums[left] {\n            j-- // Search from right to left for the first element smaller than the pivot\n        }\n        for i < j && nums[i] <= nums[left] {\n            i++ // Search from left to right for the first element greater than the pivot\n        }\n        // Swap elements\n        nums[i], nums[j] = nums[j], nums[i]\n    }\n    // Swap the pivot to the boundary between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i // Return the index of the pivot\n}\n
quick_sort.swift
/* Sentinel partition */\nfunc partition(nums: inout [Int], left: Int, right: Int) -> Int {\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while i < j {\n        while i < j, nums[j] >= nums[left] {\n            j -= 1 // Search from right to left for the first element smaller than the pivot\n        }\n        while i < j, nums[i] <= nums[left] {\n            i += 1 // Search from left to right for the first element greater than the pivot\n        }\n        nums.swapAt(i, j) // Swap these two elements\n    }\n    nums.swapAt(i, left) // Swap the pivot to the boundary between the two subarrays\n    return i // Return the index of the pivot\n}\n
quick_sort.js
/* Swap elements */\nswap(nums, i, j) {\n    let tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Sentinel partition */\npartition(nums, left, right) {\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j -= 1; // Search from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i += 1; // Search from left to right for the first element greater than the pivot\n        }\n        // Swap elements\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n    return i; // Return the index of the pivot\n}\n
quick_sort.ts
/* Swap elements */\nswap(nums: number[], i: number, j: number): void {\n    let tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Sentinel partition */\npartition(nums: number[], left: number, right: number): number {\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j -= 1; // Search from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i += 1; // Search from left to right for the first element greater than the pivot\n        }\n        // Swap elements\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n    return i; // Return the index of the pivot\n}\n
quick_sort.dart
/* Swap elements */\nvoid _swap(List<int> nums, int i, int j) {\n  int tmp = nums[i];\n  nums[i] = nums[j];\n  nums[j] = tmp;\n}\n\n/* Sentinel partition */\nint _partition(List<int> nums, int left, int right) {\n  // Use nums[left] as the pivot\n  int i = left, j = right;\n  while (i < j) {\n    while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot\n    while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot\n    _swap(nums, i, j); // Swap these two elements\n  }\n  _swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n  return i; // Return the index of the pivot\n}\n
quick_sort.rs
/* Sentinel partition */\nfn partition(nums: &mut [i32], left: usize, right: usize) -> usize {\n    // Use nums[left] as the pivot\n    let (mut i, mut j) = (left, right);\n    while i < j {\n        while i < j && nums[j] >= nums[left] {\n            j -= 1; // Search from right to left for the first element smaller than the pivot\n        }\n        while i < j && nums[i] <= nums[left] {\n            i += 1; // Search from left to right for the first element greater than the pivot\n        }\n        nums.swap(i, j); // Swap these two elements\n    }\n    nums.swap(i, left); // Swap the pivot to the boundary between the two subarrays\n    i // Return the index of the pivot\n}\n
quick_sort.c
/* Swap elements */\nvoid swap(int nums[], int i, int j) {\n    int tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Sentinel partition */\nint partition(int nums[], int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j--; // Search from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i++; // Search from left to right for the first element greater than the pivot\n        }\n        // Swap these two elements\n        swap(nums, i, j);\n    }\n    // Swap the pivot to the boundary between the two subarrays\n    swap(nums, i, left);\n    // Return the index of the pivot\n    return i;\n}\n
quick_sort.kt
/* Swap elements */\nfun swap(nums: IntArray, i: Int, j: Int) {\n    val temp = nums[i]\n    nums[i] = nums[j]\n    nums[j] = temp\n}\n\n/* Sentinel partition */\nfun partition(nums: IntArray, left: Int, right: Int): Int {\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--           // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++           // Search from left to right for the first element greater than the pivot\n        swap(nums, i, j)  // Swap these two elements\n    }\n    swap(nums, i, left)   // Swap the pivot to the boundary between the two subarrays\n    return i              // Return the index of the pivot\n}\n
quick_sort.rb
### Sentinel partition ###\ndef partition(nums, left, right)\n  # Use nums[left] as the pivot\n  i, j = left, right\n  while i < j\n    while i < j && nums[j] >= nums[left]\n      j -= 1 # Search from right to left for the first element smaller than the pivot\n    end\n    while i < j && nums[i] <= nums[left]\n      i += 1 # Search from left to right for the first element greater than the pivot\n    end\n    # Swap elements\n    nums[i], nums[j] = nums[j], nums[i]\n  end\n  # Swap the pivot to the boundary between the two subarrays\n  nums[i], nums[left] = nums[left], nums[i]\n  i # Return the index of the pivot\nend\n
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1151-algorithm-flow","level":2,"title":"11.5.1   Algorithm Flow","text":"

The overall flow of quick sort is shown in Figure 11-9.

  1. First, perform one \"sentinel partitioning\" on the original array to obtain the unsorted left sub-array and right sub-array.
  2. Then, recursively perform \"sentinel partitioning\" on the left sub-array and right sub-array respectively.
  3. Continue recursively until the sub-array length is 1, at which point sorting of the entire array is complete.

Figure 11-9   Quick sort flow

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
def quick_sort(self, nums: list[int], left: int, right: int):\n    \"\"\"Quick sort\"\"\"\n    # Terminate recursion when subarray length is 1\n    if left >= right:\n        return\n    # Sentinel partition\n    pivot = self.partition(nums, left, right)\n    # Recursively process the left subarray and right subarray\n    self.quick_sort(nums, left, pivot - 1)\n    self.quick_sort(nums, pivot + 1, right)\n
quick_sort.cpp
/* Quick sort */\nvoid quickSort(vector<int> &nums, int left, int right) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right)\n        return;\n    // Sentinel partition\n    int pivot = partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
quick_sort.java
/* Quick sort */\nvoid quickSort(int[] nums, int left, int right) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right)\n        return;\n    // Sentinel partition\n    int pivot = partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
quick_sort.cs
/* Quick sort */\nvoid QuickSort(int[] nums, int left, int right) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right)\n        return;\n    // Sentinel partition\n    int pivot = Partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    QuickSort(nums, left, pivot - 1);\n    QuickSort(nums, pivot + 1, right);\n}\n
quick_sort.go
/* Quick sort */\nfunc (q *quickSort) quickSort(nums []int, left, right int) {\n    // Terminate recursion when subarray length is 1\n    if left >= right {\n        return\n    }\n    // Sentinel partition\n    pivot := q.partition(nums, left, right)\n    // Recursively process the left subarray and right subarray\n    q.quickSort(nums, left, pivot-1)\n    q.quickSort(nums, pivot+1, right)\n}\n
quick_sort.swift
/* Quick sort */\nfunc quickSort(nums: inout [Int], left: Int, right: Int) {\n    // Terminate recursion when subarray length is 1\n    if left >= right {\n        return\n    }\n    // Sentinel partition\n    let pivot = partition(nums: &nums, left: left, right: right)\n    // Recursively process the left subarray and right subarray\n    quickSort(nums: &nums, left: left, right: pivot - 1)\n    quickSort(nums: &nums, left: pivot + 1, right: right)\n}\n
quick_sort.js
/* Quick sort */\nquickSort(nums, left, right) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right) return;\n    // Sentinel partition\n    const pivot = this.partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    this.quickSort(nums, left, pivot - 1);\n    this.quickSort(nums, pivot + 1, right);\n}\n
quick_sort.ts
/* Quick sort */\nquickSort(nums: number[], left: number, right: number): void {\n    // Terminate recursion when subarray length is 1\n    if (left >= right) {\n        return;\n    }\n    // Sentinel partition\n    const pivot = this.partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    this.quickSort(nums, left, pivot - 1);\n    this.quickSort(nums, pivot + 1, right);\n}\n
quick_sort.dart
/* Quick sort */\nvoid quickSort(List<int> nums, int left, int right) {\n  // Terminate recursion when subarray length is 1\n  if (left >= right) return;\n  // Sentinel partition\n  int pivot = _partition(nums, left, right);\n  // Recursively process the left subarray and right subarray\n  quickSort(nums, left, pivot - 1);\n  quickSort(nums, pivot + 1, right);\n}\n
quick_sort.rs
/* Quick sort */\npub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) {\n    // Terminate recursion when subarray length is 1\n    if left >= right {\n        return;\n    }\n    // Sentinel partition\n    let pivot = Self::partition(nums, left as usize, right as usize) as i32;\n    // Recursively process the left subarray and right subarray\n    Self::quick_sort(left, pivot - 1, nums);\n    Self::quick_sort(pivot + 1, right, nums);\n}\n
quick_sort.c
/* Quick sort */\nvoid quickSort(int nums[], int left, int right) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right) {\n        return;\n    }\n    // Sentinel partition\n    int pivot = partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
quick_sort.kt
/* Quick sort */\nfun quickSort(nums: IntArray, left: Int, right: Int) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right) return\n    // Sentinel partition\n    val pivot = partition(nums, left, right)\n    // Recursively process the left subarray and right subarray\n    quickSort(nums, left, pivot - 1)\n    quickSort(nums, pivot + 1, right)\n}\n
quick_sort.rb
### Quick sort class ###\ndef quick_sort(nums, left, right)\n  # Recurse when subarray length is not 1\n  if left < right\n    # Sentinel partition\n    pivot = partition(nums, left, right)\n    # Recursively process the left subarray and right subarray\n    quick_sort(nums, left, pivot - 1)\n    quick_sort(nums, pivot + 1, right)\n  end\n  nums\nend\n
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1152-algorithm-characteristics","level":2,"title":"11.5.2   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n \\log n)\\), non-adaptive sorting: In the average case, the number of recursive levels of sentinel partitioning is \\(\\log n\\), and the total number of loops at each level is \\(n\\), using \\(O(n \\log n)\\) time overall. In the worst case, each round of sentinel partitioning divides an array of length \\(n\\) into two sub-arrays of length \\(0\\) and \\(n - 1\\), at which point the number of recursive levels reaches \\(n\\), the number of loops at each level is \\(n\\), and the total time used is \\(O(n^2)\\).
  • Space complexity of \\(O(n)\\), in-place sorting: In the case where the input array is completely reversed, the worst recursive depth reaches \\(n\\), using \\(O(n)\\) stack frame space. The sorting operation is performed on the original array without the aid of an additional array.
  • Non-stable sorting: In the last step of sentinel partitioning, the pivot may be swapped to the right of equal elements.
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1153-why-is-quick-sort-fast","level":2,"title":"11.5.3   Why Is Quick Sort Fast","text":"

From the name, we can see that quick sort should have certain advantages in terms of efficiency. Although the average time complexity of quick sort is the same as \"merge sort\" and \"heap sort\", quick sort is usually more efficient, mainly for the following reasons.

  • The probability of the worst case occurring is very low: Although the worst-case time complexity of quick sort is \\(O(n^2)\\), which is not as stable as merge sort, in the vast majority of cases, quick sort can run with a time complexity of \\(O(n \\log n)\\).
  • High cache utilization: When performing sentinel partitioning operations, the system can load the entire sub-array into the cache, so element access efficiency is relatively high. Algorithms like \"heap sort\" require jump-style access to elements, thus lacking this characteristic.
  • Small constant coefficient of complexity: Among the three algorithms mentioned above, quick sort has the smallest total number of operations such as comparisons, assignments, and swaps. This is similar to the reason why \"insertion sort\" is faster than \"bubble sort\".
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1154-pivot-optimization","level":2,"title":"11.5.4   Pivot Optimization","text":"

Quick sort may have reduced time efficiency for certain inputs. Take an extreme example: suppose the input array is completely reversed. Since we select the leftmost element as the pivot, after sentinel partitioning is complete, the pivot is swapped to the rightmost end of the array, causing the left sub-array length to be \\(n - 1\\) and the right sub-array length to be \\(0\\). If we recurse down like this, each round of sentinel partitioning will have a sub-array length of \\(0\\), the divide-and-conquer strategy fails, and quick sort degrades to a form approximate to \"bubble sort\".

To avoid this situation as much as possible, we can optimize the pivot selection strategy in sentinel partitioning. For example, we can randomly select an element as the pivot. However, if luck is not good and we select a non-ideal pivot every time, efficiency is still not satisfactory.

It should be noted that programming languages usually generate \"pseudo-random numbers\". If we construct a specific test case for a pseudo-random number sequence, the efficiency of quick sort may still degrade.

For further improvement, we can select three candidate elements in the array (usually the first, last, and middle elements of the array), and use the median of these three candidate elements as the pivot. In this way, the probability that the pivot is \"neither too small nor too large\" will be greatly increased. Of course, we can also select more candidate elements to further improve the robustness of the algorithm. After adopting this method, the probability of time complexity degrading to \\(O(n^2)\\) is greatly reduced.

Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int:\n    \"\"\"Select the median of three candidate elements\"\"\"\n    l, m, r = nums[left], nums[mid], nums[right]\n    if (l <= m <= r) or (r <= m <= l):\n        return mid  # m is between l and r\n    if (m <= l <= r) or (r <= l <= m):\n        return left  # l is between m and r\n    return right\n\ndef partition(self, nums: list[int], left: int, right: int) -> int:\n    \"\"\"Sentinel partition (median of three)\"\"\"\n    # Use nums[left] as the pivot\n    med = self.median_three(nums, left, (left + right) // 2, right)\n    # Swap the median to the array's leftmost position\n    nums[left], nums[med] = nums[med], nums[left]\n    # Use nums[left] as the pivot\n    i, j = left, right\n    while i < j:\n        while i < j and nums[j] >= nums[left]:\n            j -= 1  # Search from right to left for the first element smaller than the pivot\n        while i < j and nums[i] <= nums[left]:\n            i += 1  # Search from left to right for the first element greater than the pivot\n        # Swap elements\n        nums[i], nums[j] = nums[j], nums[i]\n    # Swap the pivot to the boundary between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i  # Return the index of the pivot\n
quick_sort.cpp
/* Select the median of three candidate elements */\nint medianThree(vector<int> &nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m is between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l is between m and r\n    return right;\n}\n\n/* Sentinel partition (median of three) */\nint partition(vector<int> &nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median to the array's leftmost position\n    swap(nums[left], nums[med]);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;                // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;                // Search from left to right for the first element greater than the pivot\n        swap(nums[i], nums[j]); // Swap these two elements\n    }\n    swap(nums[i], nums[left]);  // Swap the pivot to the boundary between the two subarrays\n    return i;                   // Return the index of the pivot\n}\n
quick_sort.java
/* Select the median of three candidate elements */\nint medianThree(int[] nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m is between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l is between m and r\n    return right;\n}\n\n/* Sentinel partition (median of three) */\nint partition(int[] nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median to the array's leftmost position\n    swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Search from left to right for the first element greater than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left);  // Swap the pivot to the boundary between the two subarrays\n    return i;             // Return the index of the pivot\n}\n
quick_sort.cs
/* Select the median of three candidate elements */\nint MedianThree(int[] nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m is between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l is between m and r\n    return right;\n}\n\n/* Sentinel partition (median of three) */\nint Partition(int[] nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = MedianThree(nums, left, (left + right) / 2, right);\n    // Swap the median to the array's leftmost position\n    Swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Search from left to right for the first element greater than the pivot\n        Swap(nums, i, j); // Swap these two elements\n    }\n    Swap(nums, i, left);  // Swap the pivot to the boundary between the two subarrays\n    return i;             // Return the index of the pivot\n}\n
quick_sort.go
/* Select the median of three candidate elements */\nfunc (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int {\n    l, m, r := nums[left], nums[mid], nums[right]\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid // m is between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left // l is between m and r\n    }\n    return right\n}\n\n/* Sentinel partition (median of three) */\nfunc (q *quickSortMedian) partition(nums []int, left, right int) int {\n    // Use nums[left] as the pivot\n    med := q.medianThree(nums, left, (left+right)/2, right)\n    // Swap the median to the array's leftmost position\n    nums[left], nums[med] = nums[med], nums[left]\n    // Use nums[left] as the pivot\n    i, j := left, right\n    for i < j {\n        for i < j && nums[j] >= nums[left] {\n            j-- // Search from right to left for the first element smaller than the pivot\n        }\n        for i < j && nums[i] <= nums[left] {\n            i++ // Search from left to right for the first element greater than the pivot\n        }\n        // Swap elements\n        nums[i], nums[j] = nums[j], nums[i]\n    }\n    // Swap the pivot to the boundary between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i // Return the index of the pivot\n}\n
quick_sort.swift
/* Select the median of three candidate elements */\nfunc medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int {\n    let l = nums[left]\n    let m = nums[mid]\n    let r = nums[right]\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid // m is between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left // l is between m and r\n    }\n    return right\n}\n\n/* Sentinel partition (median of three) */\nfunc partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int {\n    // Select the median of three candidate elements\n    let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right)\n    // Swap the median to the array's leftmost position\n    nums.swapAt(left, med)\n    return partition(nums: &nums, left: left, right: right)\n}\n
quick_sort.js
/* Select the median of three candidate elements */\nmedianThree(nums, left, mid, right) {\n    let l = nums[left],\n        m = nums[mid],\n        r = nums[right];\n    // m is between l and r\n    if ((l <= m && m <= r) || (r <= m && m <= l)) return mid;\n    // l is between m and r\n    if ((m <= l && l <= r) || (r <= l && l <= m)) return left;\n    return right;\n}\n\n/* Sentinel partition (median of three) */\npartition(nums, left, right) {\n    // Select the median of three candidate elements\n    let med = this.medianThree(\n        nums,\n        left,\n        Math.floor((left + right) / 2),\n        right\n    );\n    // Swap the median to the array's leftmost position\n    this.swap(nums, left, med);\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n    return i; // Return the index of the pivot\n}\n
quick_sort.ts
/* Select the median of three candidate elements */\nmedianThree(\n    nums: number[],\n    left: number,\n    mid: number,\n    right: number\n): number {\n    let l = nums[left],\n        m = nums[mid],\n        r = nums[right];\n    // m is between l and r\n    if ((l <= m && m <= r) || (r <= m && m <= l)) return mid;\n    // l is between m and r\n    if ((m <= l && l <= r) || (r <= l && l <= m)) return left;\n    return right;\n}\n\n/* Sentinel partition (median of three) */\npartition(nums: number[], left: number, right: number): number {\n    // Select the median of three candidate elements\n    let med = this.medianThree(\n        nums,\n        left,\n        Math.floor((left + right) / 2),\n        right\n    );\n    // Swap the median to the array's leftmost position\n    this.swap(nums, left, med);\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j--; // Search from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i++; // Search from left to right for the first element greater than the pivot\n        }\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n    return i; // Return the index of the pivot\n}\n
quick_sort.dart
/* Select the median of three candidate elements */\nint _medianThree(List<int> nums, int left, int mid, int right) {\n  int l = nums[left], m = nums[mid], r = nums[right];\n  if ((l <= m && m <= r) || (r <= m && m <= l))\n    return mid; // m is between l and r\n  if ((m <= l && l <= r) || (r <= l && l <= m))\n    return left; // l is between m and r\n  return right;\n}\n\n/* Sentinel partition (median of three) */\nint _partition(List<int> nums, int left, int right) {\n  // Select the median of three candidate elements\n  int med = _medianThree(nums, left, (left + right) ~/ 2, right);\n  // Swap the median to the array's leftmost position\n  _swap(nums, left, med);\n  // Use nums[left] as the pivot\n  int i = left, j = right;\n  while (i < j) {\n    while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot\n    while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot\n    _swap(nums, i, j); // Swap these two elements\n  }\n  _swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n  return i; // Return the index of the pivot\n}\n
quick_sort.rs
/* Select the median of three candidate elements */\nfn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize {\n    let (l, m, r) = (nums[left], nums[mid], nums[right]);\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid; // m is between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left; // l is between m and r\n    }\n    right\n}\n\n/* Sentinel partition (median of three) */\nfn partition(nums: &mut [i32], left: usize, right: usize) -> usize {\n    // Select the median of three candidate elements\n    let med = Self::median_three(nums, left, (left + right) / 2, right);\n    // Swap the median to the array's leftmost position\n    nums.swap(left, med);\n    // Use nums[left] as the pivot\n    let (mut i, mut j) = (left, right);\n    while i < j {\n        while i < j && nums[j] >= nums[left] {\n            j -= 1; // Search from right to left for the first element smaller than the pivot\n        }\n        while i < j && nums[i] <= nums[left] {\n            i += 1; // Search from left to right for the first element greater than the pivot\n        }\n        nums.swap(i, j); // Swap these two elements\n    }\n    nums.swap(i, left); // Swap the pivot to the boundary between the two subarrays\n    i // Return the index of the pivot\n}\n
quick_sort.c
/* Select the median of three candidate elements */\nint medianThree(int nums[], int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m is between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l is between m and r\n    return right;\n}\n\n/* Sentinel partition (median of three) */\nint partitionMedian(int nums[], int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median to the array's leftmost position\n    swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--; // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Search from left to right for the first element greater than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n    return i;            // Return the index of the pivot\n}\n
quick_sort.kt
/* Select the median of three candidate elements */\nfun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int {\n    val l = nums[left]\n    val m = nums[mid]\n    val r = nums[right]\n    if ((m in l..r) || (m in r..l))\n        return mid  // m is between l and r\n    if ((l in m..r) || (l in r..m))\n        return left // l is between m and r\n    return right\n}\n\n/* Sentinel partition (median of three) */\nfun partitionMedian(nums: IntArray, left: Int, right: Int): Int {\n    // Select the median of three candidate elements\n    val med = medianThree(nums, left, (left + right) / 2, right)\n    // Swap the median to the array's leftmost position\n    swap(nums, left, med)\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--                      // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++                      // Search from left to right for the first element greater than the pivot\n        swap(nums, i, j)             // Swap these two elements\n    }\n    swap(nums, i, left)              // Swap the pivot to the boundary between the two subarrays\n    return i                         // Return the index of the pivot\n}\n
quick_sort.rb
### Select median of three candidate elements ###\ndef median_three(nums, left, mid, right)\n  # Select the median of three candidate elements\n  _l, _m, _r = nums[left], nums[mid], nums[right]\n  # m is between l and r\n  return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l)\n  # l is between m and r\n  return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m)\n  return right\nend\n\n### Sentinel partition (median of three) ###\ndef partition(nums, left, right)\n  ### Use nums[left] as pivot\n  med = median_three(nums, left, (left + right) / 2, right)\n  # Swap median to leftmost position of array\n  nums[left], nums[med] = nums[med], nums[left]\n  i, j = left, right\n  while i < j\n    while i < j && nums[j] >= nums[left]\n      j -= 1 # Search from right to left for the first element smaller than the pivot\n    end\n    while i < j && nums[i] <= nums[left]\n      i += 1 # Search from left to right for the first element greater than the pivot\n    end\n    # Swap elements\n    nums[i], nums[j] = nums[j], nums[i]\n  end\n  # Swap the pivot to the boundary between the two subarrays\n  nums[i], nums[left] = nums[left], nums[i]\n  i # Return the index of the pivot\nend\n
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1155-recursive-depth-optimization","level":2,"title":"11.5.5   Recursive Depth Optimization","text":"

For certain inputs, quick sort may occupy more space. Taking a completely ordered input array as an example, let the length of the sub-array in recursion be \\(m\\). Each round of sentinel partitioning will produce a left sub-array of length \\(0\\) and a right sub-array of length \\(m - 1\\), which means that the problem scale reduced per recursive call is very small (only one element is reduced), and the height of the recursion tree will reach \\(n - 1\\), at which point \\(O(n)\\) size of stack frame space is required.

To prevent the accumulation of stack frame space, we can compare the lengths of the two sub-arrays after each round of sentinel sorting is complete, and only recurse on the shorter sub-array. Since the length of the shorter sub-array will not exceed \\(n / 2\\), this method can ensure that the recursion depth does not exceed \\(\\log n\\), thus optimizing the worst-case space complexity to \\(O(\\log n)\\). The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
def quick_sort(self, nums: list[int], left: int, right: int):\n    \"\"\"Quick sort (recursion depth optimization)\"\"\"\n    # Terminate when subarray length is 1\n    while left < right:\n        # Sentinel partition operation\n        pivot = self.partition(nums, left, right)\n        # Perform quick sort on the shorter of the two subarrays\n        if pivot - left < right - pivot:\n            self.quick_sort(nums, left, pivot - 1)  # Recursively sort the left subarray\n            left = pivot + 1  # Remaining unsorted interval is [pivot + 1, right]\n        else:\n            self.quick_sort(nums, pivot + 1, right)  # Recursively sort the right subarray\n            right = pivot - 1  # Remaining unsorted interval is [left, pivot - 1]\n
quick_sort.cpp
/* Quick sort (recursion depth optimization) */\nvoid quickSort(vector<int> &nums, int left, int right) {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        int pivot = partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1;                 // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1;                 // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.java
/* Quick sort (recursion depth optimization) */\nvoid quickSort(int[] nums, int left, int right) {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        int pivot = partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.cs
/* Quick sort (recursion depth optimization) */\nvoid QuickSort(int[] nums, int left, int right) {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        int pivot = Partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            QuickSort(nums, left, pivot - 1);  // Recursively sort the left subarray\n            left = pivot + 1;  // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            QuickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.go
/* Quick sort (recursion depth optimization) */\nfunc (q *quickSortTailCall) quickSort(nums []int, left, right int) {\n    // Terminate when subarray length is 1\n    for left < right {\n        // Sentinel partition operation\n        pivot := q.partition(nums, left, right)\n        // Perform quick sort on the shorter of the two subarrays\n        if pivot-left < right-pivot {\n            q.quickSort(nums, left, pivot-1) // Recursively sort the left subarray\n            left = pivot + 1                 // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            q.quickSort(nums, pivot+1, right) // Recursively sort the right subarray\n            right = pivot - 1                 // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.swift
/* Quick sort (recursion depth optimization) */\nfunc quickSortTailCall(nums: inout [Int], left: Int, right: Int) {\n    var left = left\n    var right = right\n    // Terminate when subarray length is 1\n    while left < right {\n        // Sentinel partition operation\n        let pivot = partition(nums: &nums, left: left, right: right)\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left) < (right - pivot) {\n            quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // Recursively sort the left subarray\n            left = pivot + 1 // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // Recursively sort the right subarray\n            right = pivot - 1 // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.js
/* Quick sort (recursion depth optimization) */\nquickSort(nums, left, right) {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        let pivot = this.partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            this.quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            this.quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.ts
/* Quick sort (recursion depth optimization) */\nquickSort(nums: number[], left: number, right: number): void {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        let pivot = this.partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            this.quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            this.quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.dart
/* Quick sort (recursion depth optimization) */\nvoid quickSort(List<int> nums, int left, int right) {\n  // Terminate when subarray length is 1\n  while (left < right) {\n    // Sentinel partition operation\n    int pivot = _partition(nums, left, right);\n    // Perform quick sort on the shorter of the two subarrays\n    if (pivot - left < right - pivot) {\n      quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n      left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]\n    } else {\n      quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n      right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n    }\n  }\n}\n
quick_sort.rs
/* Quick sort (recursion depth optimization) */\npub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) {\n    // Terminate when subarray length is 1\n    while left < right {\n        // Sentinel partition operation\n        let pivot = Self::partition(nums, left as usize, right as usize) as i32;\n        // Perform quick sort on the shorter of the two subarrays\n        if pivot - left < right - pivot {\n            Self::quick_sort(left, pivot - 1, nums); // Recursively sort the left subarray\n            left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            Self::quick_sort(pivot + 1, right, nums); // Recursively sort the right subarray\n            right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.c
/* Quick sort (recursion depth optimization) */\nvoid quickSortTailCall(int nums[], int left, int right) {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        int pivot = partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            // Recursively sort the left subarray\n            quickSortTailCall(nums, left, pivot - 1);\n            // Remaining unsorted interval is [pivot + 1, right]\n            left = pivot + 1;\n        } else {\n            // Recursively sort the right subarray\n            quickSortTailCall(nums, pivot + 1, right);\n            // Remaining unsorted interval is [left, pivot - 1]\n            right = pivot - 1;\n        }\n    }\n}\n
quick_sort.kt
/* Quick sort (recursion depth optimization) */\nfun quickSortTailCall(nums: IntArray, left: Int, right: Int) {\n    // Terminate when subarray length is 1\n    var l = left\n    var r = right\n    while (l < r) {\n        // Sentinel partition operation\n        val pivot = partition(nums, l, r)\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - l < r - pivot) {\n            quickSort(nums, l, pivot - 1) // Recursively sort the left subarray\n            l = pivot + 1 // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            quickSort(nums, pivot + 1, r) // Recursively sort the right subarray\n            r = pivot - 1 // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.rb
### Quick sort (recursion depth optimization) ###\ndef quick_sort(nums, left, right)\n  # Recurse when subarray length is not 1\n  while left < right\n    # Sentinel partition\n    pivot = partition(nums, left, right)\n    # Perform quick sort on the shorter of the two subarrays\n    if pivot - left < right - pivot\n      quick_sort(nums, left, pivot - 1)\n      left = pivot + 1 # Remaining unsorted interval is [pivot + 1, right]\n    else\n      quick_sort(nums, pivot + 1, right)\n      right = pivot - 1 # Remaining unsorted interval is [left, pivot - 1]\n    end\n  end\nend\n
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/","level":1,"title":"11.10   Radix Sort","text":"

The previous section introduced counting sort, which is suitable for situations where the data volume \\(n\\) is large but the data range \\(m\\) is small. Suppose we need to sort \\(n = 10^6\\) student IDs, and the student ID is an 8-digit number, which means the data range \\(m = 10^8\\) is very large. Using counting sort would require allocating a large amount of memory space, whereas radix sort can avoid this situation.

Radix sort (radix sort) has a core idea consistent with counting sort, which also achieves sorting by counting quantities. Building on this, radix sort utilizes the progressive relationship between the digits of numbers, sorting each digit in turn to obtain the final sorting result.

","path":["Chapter 11. Sorting","11.10   Radix Sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/#11101-algorithm-flow","level":2,"title":"11.10.1   Algorithm Flow","text":"

Taking student ID data as an example, assume the lowest digit is the \\(1\\)st digit and the highest digit is the \\(8\\)th digit. The flow of radix sort is shown in Figure 11-18.

  1. Initialize the digit \\(k = 1\\).
  2. Perform \"counting sort\" on the \\(k\\)th digit of the student IDs. After completion, the data will be sorted from smallest to largest according to the \\(k\\)th digit.
  3. Increase \\(k\\) by \\(1\\), then return to step 2. and continue iterating until all digits are sorted, at which point the process ends.

Figure 11-18   Radix sort algorithm flow

Below we analyze the code implementation. For a \\(d\\)-base number \\(x\\), to get its \\(k\\)th digit \\(x_k\\), the following calculation formula can be used:

\\[ x_k = \\lfloor\\frac{x}{d^{k-1}}\\rfloor \\bmod d \\]

Where \\(\\lfloor a \\rfloor\\) denotes rounding down the floating-point number \\(a\\), and \\(\\bmod \\: d\\) denotes taking the modulo (remainder) with respect to \\(d\\). For student ID data, \\(d = 10\\) and \\(k \\in [1, 8]\\).

Additionally, we need to slightly modify the counting sort code to make it sort based on the \\(k\\)th digit of the number:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby radix_sort.py
def digit(num: int, exp: int) -> int:\n    \"\"\"Get the k-th digit of element num, where exp = 10^(k-1)\"\"\"\n    # Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num // exp) % 10\n\ndef counting_sort_digit(nums: list[int], exp: int):\n    \"\"\"Counting sort (based on nums k-th digit)\"\"\"\n    # Decimal digit range is 0~9, therefore need a bucket array of length 10\n    counter = [0] * 10\n    n = len(nums)\n    # Count the occurrence of digits 0~9\n    for i in range(n):\n        d = digit(nums[i], exp)  # Get the k-th digit of nums[i], noted as d\n        counter[d] += 1  # Count the occurrence of digit d\n    # Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for i in range(1, 10):\n        counter[i] += counter[i - 1]\n    # Traverse in reverse, based on bucket statistics, place each element into res\n    res = [0] * n\n    for i in range(n - 1, -1, -1):\n        d = digit(nums[i], exp)\n        j = counter[d] - 1  # Get the index j for d in the array\n        res[j] = nums[i]  # Place the current element at index j\n        counter[d] -= 1  # Decrease the count of d by 1\n    # Use result to overwrite the original array nums\n    for i in range(n):\n        nums[i] = res[i]\n\ndef radix_sort(nums: list[int]):\n    \"\"\"Radix sort\"\"\"\n    # Get the maximum element of the array, used to determine the maximum number of digits\n    m = max(nums)\n    # Traverse from the lowest to the highest digit\n    exp = 1\n    while exp <= m:\n        # Perform counting sort on the k-th digit of array elements\n        # k = 1 -> exp = 1\n        # k = 2 -> exp = 10\n        # i.e., exp = 10^(k-1)\n        counting_sort_digit(nums, exp)\n        exp *= 10\n
radix_sort.cpp
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nvoid countingSortDigit(vector<int> &nums, int exp) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    vector<int> counter(10, 0);\n    int n = nums.size();\n    // Count the occurrence of digits 0~9\n    for (int i = 0; i < n; i++) {\n        int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d]++;                // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    vector<int> res(n, 0);\n    for (int i = n - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (int i = 0; i < n; i++)\n        nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(vector<int> &nums) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    int m = *max_element(nums.begin(), nums.end());\n    // Traverse from the lowest to the highest digit\n    for (int exp = 1; exp <= m; exp *= 10)\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n}\n
radix_sort.java
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nvoid countingSortDigit(int[] nums, int exp) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    int[] counter = new int[10];\n    int n = nums.length;\n    // Count the occurrence of digits 0~9\n    for (int i = 0; i < n; i++) {\n        int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d]++;                // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (int i = 0; i < n; i++)\n        nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(int[] nums) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    int m = Integer.MIN_VALUE;\n    for (int num : nums)\n        if (num > m)\n            m = num;\n    // Traverse from the lowest to the highest digit\n    for (int exp = 1; exp <= m; exp *= 10) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
radix_sort.cs
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nint Digit(int num, int exp) {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nvoid CountingSortDigit(int[] nums, int exp) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    int[] counter = new int[10];\n    int n = nums.Length;\n    // Count the occurrence of digits 0~9\n    for (int i = 0; i < n; i++) {\n        int d = Digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d]++;                // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int d = Digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nvoid RadixSort(int[] nums) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    int m = int.MinValue;\n    foreach (int num in nums) {\n        if (num > m) m = num;\n    }\n    // Traverse from the lowest to the highest digit\n    for (int exp = 1; exp <= m; exp *= 10) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        CountingSortDigit(nums, exp);\n    }\n}\n
radix_sort.go
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfunc digit(num, exp int) int {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10\n}\n\n/* Counting sort (based on nums k-th digit) */\nfunc countingSortDigit(nums []int, exp int) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    counter := make([]int, 10)\n    n := len(nums)\n    // Count the occurrence of digits 0~9\n    for i := 0; i < n; i++ {\n        d := digit(nums[i], exp) // Get the k-th digit of nums[i], noted as d\n        counter[d]++             // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for i := 1; i < 10; i++ {\n        counter[i] += counter[i-1]\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    res := make([]int, n)\n    for i := n - 1; i >= 0; i-- {\n        d := digit(nums[i], exp)\n        j := counter[d] - 1 // Get the index j for d in the array\n        res[j] = nums[i]    // Place the current element at index j\n        counter[d]--        // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for i := 0; i < n; i++ {\n        nums[i] = res[i]\n    }\n}\n\n/* Radix sort */\nfunc radixSort(nums []int) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    max := math.MinInt\n    for _, num := range nums {\n        if num > max {\n            max = num\n        }\n    }\n    // Traverse from the lowest to the highest digit\n    for exp := 1; max >= exp; exp *= 10 {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp)\n    }\n}\n
radix_sort.swift
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfunc digit(num: Int, exp: Int) -> Int {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    (num / exp) % 10\n}\n\n/* Counting sort (based on nums k-th digit) */\nfunc countingSortDigit(nums: inout [Int], exp: Int) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    var counter = Array(repeating: 0, count: 10)\n    // Count the occurrence of digits 0~9\n    for i in nums.indices {\n        let d = digit(num: nums[i], exp: exp) // Get the k-th digit of nums[i], noted as d\n        counter[d] += 1 // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for i in 1 ..< 10 {\n        counter[i] += counter[i - 1]\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    var res = Array(repeating: 0, count: nums.count)\n    for i in nums.indices.reversed() {\n        let d = digit(num: nums[i], exp: exp)\n        let j = counter[d] - 1 // Get the index j for d in the array\n        res[j] = nums[i] // Place the current element at index j\n        counter[d] -= 1 // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for i in nums.indices {\n        nums[i] = res[i]\n    }\n}\n\n/* Radix sort */\nfunc radixSort(nums: inout [Int]) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    var m = Int.min\n    for num in nums {\n        if num > m {\n            m = num\n        }\n    }\n    // Traverse from the lowest to the highest digit\n    for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums: &nums, exp: exp)\n    }\n}\n
radix_sort.js
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfunction digit(num, exp) {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return Math.floor(num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nfunction countingSortDigit(nums, exp) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    const counter = new Array(10).fill(0);\n    const n = nums.length;\n    // Count the occurrence of digits 0~9\n    for (let i = 0; i < n; i++) {\n        const d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d]++; // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (let i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    const res = new Array(n).fill(0);\n    for (let i = n - 1; i >= 0; i--) {\n        const d = digit(nums[i], exp);\n        const j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d]--; // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nfunction radixSort(nums) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    let m = Math.max(... nums);\n    // Traverse from the lowest to the highest digit\n    for (let exp = 1; exp <= m; exp *= 10) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
radix_sort.ts
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfunction digit(num: number, exp: number): number {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return Math.floor(num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nfunction countingSortDigit(nums: number[], exp: number): void {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    const counter = new Array(10).fill(0);\n    const n = nums.length;\n    // Count the occurrence of digits 0~9\n    for (let i = 0; i < n; i++) {\n        const d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d]++; // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (let i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    const res = new Array(n).fill(0);\n    for (let i = n - 1; i >= 0; i--) {\n        const d = digit(nums[i], exp);\n        const j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d]--; // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nfunction radixSort(nums: number[]): void {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    let m: number = Math.max(... nums);\n    // Traverse from the lowest to the highest digit\n    for (let exp = 1; exp <= m; exp *= 10) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
radix_sort.dart
/* Get k-th digit of element _num, where exp = 10^(k-1) */\nint digit(int _num, int exp) {\n  // Passing exp instead of k can avoid repeated expensive exponentiation here\n  return (_num ~/ exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nvoid countingSortDigit(List<int> nums, int exp) {\n  // Decimal digit range is 0~9, therefore need a bucket array of length 10\n  List<int> counter = List<int>.filled(10, 0);\n  int n = nums.length;\n  // Count the occurrence of digits 0~9\n  for (int i = 0; i < n; i++) {\n    int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n    counter[d]++; // Count the occurrence of digit d\n  }\n  // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n  for (int i = 1; i < 10; i++) {\n    counter[i] += counter[i - 1];\n  }\n  // Traverse in reverse, based on bucket statistics, place each element into res\n  List<int> res = List<int>.filled(n, 0);\n  for (int i = n - 1; i >= 0; i--) {\n    int d = digit(nums[i], exp);\n    int j = counter[d] - 1; // Get the index j for d in the array\n    res[j] = nums[i]; // Place the current element at index j\n    counter[d]--; // Decrease the count of d by 1\n  }\n  // Use result to overwrite the original array nums\n  for (int i = 0; i < n; i++) nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(List<int> nums) {\n  // Get the maximum element of the array, used to determine the maximum number of digits\n  // In Dart, int length is 64 bits\n  int m = -1 << 63;\n  for (int _num in nums) if (_num > m) m = _num;\n  // Traverse from the lowest to the highest digit\n  for (int exp = 1; exp <= m; exp *= 10)\n    // Perform counting sort on the k-th digit of array elements\n    // k = 1 -> exp = 1\n    // k = 2 -> exp = 10\n    // i.e., exp = 10^(k-1)\n    countingSortDigit(nums, exp);\n}\n
radix_sort.rs
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfn digit(num: i32, exp: i32) -> usize {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return ((num / exp) % 10) as usize;\n}\n\n/* Counting sort (based on nums k-th digit) */\nfn counting_sort_digit(nums: &mut [i32], exp: i32) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    let mut counter = [0; 10];\n    let n = nums.len();\n    // Count the occurrence of digits 0~9\n    for i in 0..n {\n        let d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d] += 1; // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for i in 1..10 {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    let mut res = vec![0; n];\n    for i in (0..n).rev() {\n        let d = digit(nums[i], exp);\n        let j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d] -= 1; // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    nums.copy_from_slice(&res);\n}\n\n/* Radix sort */\nfn radix_sort(nums: &mut [i32]) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    let m = *nums.into_iter().max().unwrap();\n    // Traverse from the lowest to the highest digit\n    let mut exp = 1;\n    while exp <= m {\n        counting_sort_digit(nums, exp);\n        exp *= 10;\n    }\n}\n
radix_sort.c
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nvoid countingSortDigit(int nums[], int size, int exp) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    int *counter = (int *)malloc((sizeof(int) * 10));\n    memset(counter, 0, sizeof(int) * 10); // Initialize to 0 to support subsequent memory release\n    // Count the occurrence of digits 0~9\n    for (int i = 0; i < size; i++) {\n        // Get the k-th digit of nums[i], noted as d\n        int d = digit(nums[i], exp);\n        // Count the occurrence of digit d\n        counter[d]++;\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    int *res = (int *)malloc(sizeof(int) * size);\n    for (int i = size - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (int i = 0; i < size; i++) {\n        nums[i] = res[i];\n    }\n    // Free memory\n    free(res);\n    free(counter);\n}\n\n/* Radix sort */\nvoid radixSort(int nums[], int size) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    int max = INT32_MIN;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > max) {\n            max = nums[i];\n        }\n    }\n    // Traverse from the lowest to the highest digit\n    for (int exp = 1; max >= exp; exp *= 10)\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, size, exp);\n}\n
radix_sort.kt
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfun digit(num: Int, exp: Int): Int {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10\n}\n\n/* Counting sort (based on nums k-th digit) */\nfun countingSortDigit(nums: IntArray, exp: Int) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    val counter = IntArray(10)\n    val n = nums.size\n    // Count the occurrence of digits 0~9\n    for (i in 0..<n) {\n        val d = digit(nums[i], exp) // Get the k-th digit of nums[i], noted as d\n        counter[d]++                // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (i in 1..9) {\n        counter[i] += counter[i - 1]\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    val res = IntArray(n)\n    for (i in n - 1 downTo 0) {\n        val d = digit(nums[i], exp)\n        val j = counter[d] - 1 // Get the index j for d in the array\n        res[j] = nums[i]       // Place the current element at index j\n        counter[d]--           // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (i in 0..<n)\n        nums[i] = res[i]\n}\n\n/* Radix sort */\nfun radixSort(nums: IntArray) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    var m = Int.MIN_VALUE\n    for (num in nums) if (num > m) m = num\n    var exp = 1\n    // Traverse from the lowest to the highest digit\n    while (exp <= m) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp)\n        exp *= 10\n    }\n}\n
radix_sort.rb
### Get k-th digit of element num, where exp = 10^(k-1) ###\ndef digit(num, exp)\n  # Passing exp instead of k avoids expensive exponentiation calculations\n  (num / exp) % 10\nend\n\n### Counting sort (sort by k-th digit of nums) ###\ndef counting_sort_digit(nums, exp)\n  # Decimal digit range is 0~9, therefore need a bucket array of length 10\n  counter = Array.new(10, 0)\n  n = nums.length\n  # Count the occurrence of digits 0~9\n  for i in 0...n\n    d = digit(nums[i], exp) # Get the k-th digit of nums[i], noted as d\n    counter[d] += 1 # Count the occurrence of digit d\n  end\n  # Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n  (1...10).each { |i| counter[i] += counter[i - 1] }\n  # Traverse in reverse, based on bucket statistics, place each element into res\n  res = Array.new(n, 0)\n  for i in (n - 1).downto(0)\n    d = digit(nums[i], exp)\n    j = counter[d] - 1 # Get the index j for d in the array\n    res[j] = nums[i] # Place the current element at index j\n    counter[d] -= 1 # Decrease the count of d by 1\n  end\n  # Use result to overwrite the original array nums\n  (0...n).each { |i| nums[i] = res[i] }\nend\n\n### Radix sort ###\ndef radix_sort(nums)\n  # Get the maximum element of the array, used to determine the maximum number of digits\n  m = nums.max\n  # Traverse from the lowest to the highest digit\n  exp = 1\n  while exp <= m\n    # Perform counting sort on the k-th digit of array elements\n    # k = 1 -> exp = 1\n    # k = 2 -> exp = 10\n    # i.e., exp = 10^(k-1)\n    counting_sort_digit(nums, exp)\n    exp *= 10\n  end\nend\n

Why start sorting from the lowest digit?

In successive sorting rounds, the result of a later round will override the result of an earlier round. For example, if the first round result is \\(a < b\\), while the second round result is \\(a > b\\), then the second round's result will replace the first round's result. Since higher-order digits have higher priority than lower-order digits, we should sort the lower digits first and then sort the higher digits.

","path":["Chapter 11. Sorting","11.10   Radix Sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/#11102-algorithm-characteristics","level":2,"title":"11.10.2   Algorithm Characteristics","text":"

Compared to counting sort, radix sort is suitable for larger numerical ranges, but the prerequisite is that the data must be representable in a fixed number of digits, and the number of digits should not be too large. For example, floating-point numbers are not suitable for radix sort because their number of digits \\(k\\) may be too large, potentially leading to time complexity \\(O(nk) \\gg O(n^2)\\).

  • Time complexity of \\(O(nk)\\), non-adaptive sorting: Let the data volume be \\(n\\), the data be in base \\(d\\), and the maximum number of digits be \\(k\\). Then performing counting sort on a certain digit uses \\(O(n + d)\\) time, and sorting all \\(k\\) digits uses \\(O((n + d)k)\\) time. Typically, both \\(d\\) and \\(k\\) are relatively small, and the time complexity approaches \\(O(n)\\).
  • Space complexity of \\(O(n + d)\\), non-in-place sorting: Same as counting sort, radix sort requires auxiliary arrays res and counter of lengths \\(n\\) and \\(d\\).
  • Stable sorting: When counting sort is stable, radix sort is also stable; when counting sort is unstable, radix sort cannot guarantee obtaining correct sorting results.
","path":["Chapter 11. Sorting","11.10   Radix Sort"],"tags":[]},{"location":"chapter_sorting/selection_sort/","level":1,"title":"11.2   Selection Sort","text":"

Selection sort (selection sort) works very simply: it opens a loop, and in each round, selects the smallest element from the unsorted interval and places it at the end of the sorted interval.

Assume the array has length \\(n\\). The algorithm flow of selection sort is shown in Figure 11-2.

  1. Initially, all elements are unsorted, i.e., the unsorted (index) interval is \\([0, n-1]\\).
  2. Select the smallest element in the interval \\([0, n-1]\\) and swap it with the element at index \\(0\\). After completion, the first element of the array is sorted.
  3. Select the smallest element in the interval \\([1, n-1]\\) and swap it with the element at index \\(1\\). After completion, the first 2 elements of the array are sorted.
  4. And so on. After \\(n - 1\\) rounds of selection and swapping, the first \\(n - 1\\) elements of the array are sorted.
  5. The only remaining element must be the largest element, requiring no sorting, so the array sorting is complete.
<1><2><3><4><5><6><7><8><9><10><11>

Figure 11-2   Selection sort steps

In the code, we use \\(k\\) to record the smallest element within the unsorted interval:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby selection_sort.py
def selection_sort(nums: list[int]):\n    \"\"\"Selection sort\"\"\"\n    n = len(nums)\n    # Outer loop: unsorted interval is [i, n-1]\n    for i in range(n - 1):\n        # Inner loop: find the smallest element within the unsorted interval\n        k = i\n        for j in range(i + 1, n):\n            if nums[j] < nums[k]:\n                k = j  # Record the index of the smallest element\n        # Swap the smallest element with the first element of the unsorted interval\n        nums[i], nums[k] = nums[k], nums[i]\n
selection_sort.cpp
/* Selection sort */\nvoid selectionSort(vector<int> &nums) {\n    int n = nums.size();\n    // Outer loop: unsorted interval is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        swap(nums[i], nums[k]);\n    }\n}\n
selection_sort.java
/* Selection sort */\nvoid selectionSort(int[] nums) {\n    int n = nums.length;\n    // Outer loop: unsorted interval is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        int temp = nums[i];\n        nums[i] = nums[k];\n        nums[k] = temp;\n    }\n}\n
selection_sort.cs
/* Selection sort */\nvoid SelectionSort(int[] nums) {\n    int n = nums.Length;\n    // Outer loop: unsorted interval is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        (nums[k], nums[i]) = (nums[i], nums[k]);\n    }\n}\n
selection_sort.go
/* Selection sort */\nfunc selectionSort(nums []int) {\n    n := len(nums)\n    // Outer loop: unsorted interval is [i, n-1]\n    for i := 0; i < n-1; i++ {\n        // Inner loop: find the smallest element within the unsorted interval\n        k := i\n        for j := i + 1; j < n; j++ {\n            if nums[j] < nums[k] {\n                // Record the index of the smallest element\n                k = j\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        nums[i], nums[k] = nums[k], nums[i]\n\n    }\n}\n
selection_sort.swift
/* Selection sort */\nfunc selectionSort(nums: inout [Int]) {\n    // Outer loop: unsorted interval is [i, n-1]\n    for i in nums.indices.dropLast() {\n        // Inner loop: find the smallest element within the unsorted interval\n        var k = i\n        for j in nums.indices.dropFirst(i + 1) {\n            if nums[j] < nums[k] {\n                k = j // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        nums.swapAt(i, k)\n    }\n}\n
selection_sort.js
/* Selection sort */\nfunction selectionSort(nums) {\n    let n = nums.length;\n    // Outer loop: unsorted interval is [i, n-1]\n    for (let i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        let k = i;\n        for (let j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k]) {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        [nums[i], nums[k]] = [nums[k], nums[i]];\n    }\n}\n
selection_sort.ts
/* Selection sort */\nfunction selectionSort(nums: number[]): void {\n    let n = nums.length;\n    // Outer loop: unsorted interval is [i, n-1]\n    for (let i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        let k = i;\n        for (let j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k]) {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        [nums[i], nums[k]] = [nums[k], nums[i]];\n    }\n}\n
selection_sort.dart
/* Selection sort */\nvoid selectionSort(List<int> nums) {\n  int n = nums.length;\n  // Outer loop: unsorted interval is [i, n-1]\n  for (int i = 0; i < n - 1; i++) {\n    // Inner loop: find the smallest element within the unsorted interval\n    int k = i;\n    for (int j = i + 1; j < n; j++) {\n      if (nums[j] < nums[k]) k = j; // Record the index of the smallest element\n    }\n    // Swap the smallest element with the first element of the unsorted interval\n    int temp = nums[i];\n    nums[i] = nums[k];\n    nums[k] = temp;\n  }\n}\n
selection_sort.rs
/* Selection sort */\nfn selection_sort(nums: &mut [i32]) {\n    if nums.is_empty() {\n        return;\n    }\n    let n = nums.len();\n    // Outer loop: unsorted interval is [i, n-1]\n    for i in 0..n - 1 {\n        // Inner loop: find the smallest element within the unsorted interval\n        let mut k = i;\n        for j in i + 1..n {\n            if nums[j] < nums[k] {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        nums.swap(i, k);\n    }\n}\n
selection_sort.c
/* Selection sort */\nvoid selectionSort(int nums[], int n) {\n    // Outer loop: unsorted interval is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        int temp = nums[i];\n        nums[i] = nums[k];\n        nums[k] = temp;\n    }\n}\n
selection_sort.kt
/* Selection sort */\nfun selectionSort(nums: IntArray) {\n    val n = nums.size\n    // Outer loop: unsorted interval is [i, n-1]\n    for (i in 0..<n - 1) {\n        var k = i\n        // Inner loop: find the smallest element within the unsorted interval\n        for (j in i + 1..<n) {\n            if (nums[j] < nums[k])\n                k = j // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        val temp = nums[i]\n        nums[i] = nums[k]\n        nums[k] = temp\n    }\n}\n
selection_sort.rb
### Selection sort ###\ndef selection_sort(nums)\n  n = nums.length\n  # Outer loop: unsorted interval is [i, n-1]\n  for i in 0...(n - 1)\n    # Inner loop: find the smallest element within the unsorted interval\n    k = i\n    for j in (i + 1)...n\n      if nums[j] < nums[k]\n        k = j # Record the index of the smallest element\n      end\n    end\n    # Swap the smallest element with the first element of the unsorted interval\n    nums[i], nums[k] = nums[k], nums[i]\n  end\nend\n
","path":["Chapter 11. Sorting","11.2   Selection Sort"],"tags":[]},{"location":"chapter_sorting/selection_sort/#1121-algorithm-characteristics","level":2,"title":"11.2.1   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n^2)\\), non-adaptive sorting: The outer loop has \\(n - 1\\) rounds in total. The length of the unsorted interval in the first round is \\(n\\), and the length of the unsorted interval in the last round is \\(2\\). That is, each round of the outer loop contains \\(n\\), \\(n - 1\\), \\(\\dots\\), \\(3\\), \\(2\\) inner loop iterations, summing to \\(\\frac{(n - 1)(n + 2)}{2}\\).
  • Space complexity of \\(O(1)\\), in-place sorting: Pointers \\(i\\) and \\(j\\) use a constant amount of extra space.
  • Non-stable sorting: As shown in Figure 11-3, element nums[i] may be swapped to the right of an element equal to it, causing a change in their relative order.

Figure 11-3   Selection sort non-stability example

","path":["Chapter 11. Sorting","11.2   Selection Sort"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/","level":1,"title":"11.1   Sorting Algorithm","text":"

Sorting algorithm (sorting algorithm) is used to arrange a group of data in a specific order. Sorting algorithms have extensive applications because ordered data can usually be searched, analyzed, and processed more efficiently.

As shown in Figure 11-1, data types in sorting algorithms can be integers, floating-point numbers, characters, or strings, etc. The sorting criterion can be set according to requirements, such as numerical size, character ASCII code order, or custom rules.

Figure 11-1   Data type and criterion examples

","path":["Chapter 11. Sorting","11.1   Sorting Algorithm"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/#1111-evaluation-dimensions","level":2,"title":"11.1.1   Evaluation Dimensions","text":"

Execution efficiency: We expect the time complexity of sorting algorithms to be as low as possible, with a smaller total number of operations (reducing the constant factor in time complexity). For large data volumes, execution efficiency is particularly important.

In-place property: As the name implies, in-place sorting achieves sorting by operating directly on the original array without requiring additional auxiliary arrays, thus saving memory. Typically, in-place sorting involves fewer data movement operations and runs faster.

Stability: Stable sorting ensures that the relative order of equal elements in the array does not change after sorting is completed.

Stable sorting is a necessary condition for multi-level sorting scenarios. Suppose we have a table storing student information, where column 1 and column 2 are name and age, respectively. In this case, unstable sorting may cause the ordered nature of the input data to be lost:

# Input Data Is Sorted by Name\n# (name, age)\n  ('A', 19)\n  ('B', 18)\n  ('C', 21)\n  ('D', 19)\n  ('E', 23)\n\n# Assuming We Use an Unstable Sorting Algorithm to Sort the List by Age,\n# In the Result, the Relative Positions of ('D', 19) and ('A', 19) Are Changed,\n# And the Property That the Input Data Is Sorted by Name Is Lost\n  ('B', 18)\n  ('D', 19)\n  ('A', 19)\n  ('C', 21)\n  ('E', 23)\n

Adaptability: Adaptive sorting can utilize the existing order information in the input data to reduce the amount of computation, achieving better time efficiency. The best-case time complexity of adaptive sorting algorithms is typically better than the average time complexity.

Comparison-based or not: Comparison-based sorting relies on comparison operators (\\(<\\), \\(=\\), \\(>\\)) to determine the relative order of elements, thereby sorting the entire array, with a theoretical optimal time complexity of \\(O(n \\log n)\\). Non-comparison sorting does not use comparison operators and can achieve a time complexity of \\(O(n)\\), but its versatility is relatively limited.

","path":["Chapter 11. Sorting","11.1   Sorting Algorithm"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/#1112-ideal-sorting-algorithm","level":2,"title":"11.1.2   Ideal Sorting Algorithm","text":"

Fast execution, in-place, stable, adaptive, good versatility. Clearly, no sorting algorithm has been discovered to date that combines all of these characteristics. Therefore, when selecting a sorting algorithm, it is necessary to decide based on the specific characteristics of the data and the requirements of the problem.

Next, we will learn about various sorting algorithms together and analyze the advantages and disadvantages of each sorting algorithm based on the above evaluation dimensions.

","path":["Chapter 11. Sorting","11.1   Sorting Algorithm"],"tags":[]},{"location":"chapter_sorting/summary/","level":1,"title":"11.11   Summary","text":"","path":["Chapter 11. Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_sorting/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Bubble sort achieves sorting by swapping adjacent elements. By adding a flag to enable early return, we can optimize the best-case time complexity of bubble sort to \\(O(n)\\).
  • Insertion sort completes sorting by inserting elements from the unsorted interval into the correct position in the sorted interval each round. Although the time complexity of insertion sort is \\(O(n^2)\\), it is very popular in small data volume sorting tasks because it involves relatively few unit operations.
  • Quick sort is implemented based on sentinel partitioning operations. In sentinel partitioning, it is possible to select the worst pivot every time, causing the time complexity to degrade to \\(O(n^2)\\). Introducing median pivot or random pivot can reduce the probability of such degradation. By preferentially recursing on the shorter sub-interval, the recursion depth can be effectively reduced, optimizing the space complexity to \\(O(\\log n)\\).
  • Merge sort includes two phases: divide and merge, which typically embody the divide-and-conquer strategy. In merge sort, sorting an array requires creating auxiliary arrays, with a space complexity of \\(O(n)\\); however, the space complexity of sorting a linked list can be optimized to \\(O(1)\\).
  • Bucket sort consists of three steps: distributing data into buckets, sorting within buckets, and merging results. It also embodies the divide-and-conquer strategy and is suitable for very large data volumes. The key to bucket sort is distributing data evenly.
  • Counting sort is a special case of bucket sort, which achieves sorting by counting the number of occurrences of data. Counting sort is suitable for situations where the data volume is large but the data range is limited, and requires that data can be converted to positive integers.
  • Radix sort achieves data sorting by sorting digit by digit, requiring that data can be represented as fixed-digit numbers.
  • Overall, we hope to find a sorting algorithm that is efficient, stable, in-place, and adaptive, with good versatility. However, just like other data structures and algorithms, no sorting algorithm has been found so far that simultaneously possesses all these characteristics. In practical applications, we need to select the appropriate sorting algorithm based on the specific characteristics of the data.
  • Figure 11-19 compares mainstream sorting algorithms in terms of efficiency, stability, in-place property, and adaptability.

Figure 11-19   Sorting algorithm comparison

","path":["Chapter 11. Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_sorting/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: In what situations is the stability of sorting algorithms necessary?

In reality, we may sort based on a certain attribute of objects. For example, students have two attributes: name and height. We want to implement multi-level sorting: first sort by name to get (A, 180) (B, 185) (C, 170) (D, 170); then sort by height. Because the sorting algorithm is unstable, we may get (D, 170) (C, 170) (A, 180) (B, 185).

It can be seen that the positions of students D and C have been swapped, and the orderliness of names has been disrupted, which is something we don't want to see.

Q: Can the order of \"searching from right to left\" and \"searching from left to right\" in sentinel partitioning be swapped?

No. When we use the leftmost element as the pivot, we must first \"search from right to left\" and then \"search from left to right\". This conclusion is somewhat counterintuitive; let's analyze the reason.

The last step of sentinel partitioning partition() is to swap nums[left] and nums[i]. After the swap is complete, the elements to the left of the pivot are all <= the pivot, which requires that nums[left] >= nums[i] must hold before the last swap. Suppose we first \"search from left to right\", then if we cannot find an element larger than the pivot, we will exit the loop when i == j, at which point it may be that nums[j] == nums[i] > nums[left]. In other words, the last swap operation will swap an element larger than the pivot to the leftmost end of the array, causing sentinel partitioning to fail.

For example, given the array [0, 0, 0, 0, 1], if we first \"search from left to right\", the array after sentinel partitioning is [1, 0, 0, 0, 0], which is incorrect.

Thinking deeper, if we select nums[right] as the pivot, then it's exactly the opposite - we must first \"search from left to right\".

Q: Regarding the optimization of recursion depth in quick sort, why can selecting the shorter array ensure that the recursion depth does not exceed \\(\\log n\\)?

The recursion depth is the number of currently unreturned recursive methods. Each round of sentinel partitioning divides the original array into two sub-arrays. After recursion depth optimization, the length of the sub-array to be recursively processed is at most half of the original array length. Assuming the worst case is always half the length, the final recursion depth will be \\(\\log n\\).

Reviewing the original quick sort, we may continuously recurse on the longer array. In the worst case, it would be \\(n\\), \\(n - 1\\), \\(\\dots\\), \\(2\\), \\(1\\), with a recursion depth of \\(n\\). Recursion depth optimization can avoid this situation.

Q: When all elements in the array are equal, is the time complexity of quick sort \\(O(n^2)\\)? How should this degenerate case be handled?

Yes. For this situation, consider partitioning the array into three parts through sentinel partitioning: less than, equal to, and greater than the pivot. Only recursively process the less than and greater than parts. Under this method, an array where all input elements are equal can complete sorting in just one round of sentinel partitioning.

Q: Why is the worst-case time complexity of bucket sort \\(O(n^2)\\)?

In the worst case, all elements are distributed into the same bucket. If we use an \\(O(n^2)\\) algorithm to sort these elements, the time complexity will be \\(O(n^2)\\).

","path":["Chapter 11. Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/","level":1,"title":"Chapter 5.   Stack and Queue","text":"

Abstract

Stacks are like stacking cats, while queues are like cats lining up.

They represent LIFO (Last In First Out) and FIFO (First In First Out) logic, respectively.

","path":["Chapter 5. Stack and Queue","Chapter 5.   Stack and Queue"],"tags":[]},{"location":"chapter_stack_and_queue/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 5.1   Stack
  • 5.2   Queue
  • 5.3   Double-Ended Queue
  • 5.4   Summary
","path":["Chapter 5. Stack and Queue","Chapter 5.   Stack and Queue"],"tags":[]},{"location":"chapter_stack_and_queue/deque/","level":1,"title":"5.3   Deque","text":"

In a queue, we can only remove elements from the front or add elements at the rear. As shown in Figure 5-7, a double-ended queue (deque) provides greater flexibility, allowing the addition or removal of elements at both the front and rear.

Figure 5-7   Operations of deque

","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#531-common-deque-operations","level":2,"title":"5.3.1   Common Deque Operations","text":"

The common operations on a deque are shown in Table 5-3. The specific method names depend on the programming language used.

Table 5-3   Efficiency of Deque Operations

Method Description Time Complexity push_first() Add element to front \\(O(1)\\) push_last() Add element to rear \\(O(1)\\) pop_first() Remove front element \\(O(1)\\) pop_last() Remove rear element \\(O(1)\\) peek_first() Access front element \\(O(1)\\) peek_last() Access rear element \\(O(1)\\)

Similarly, we can directly use the deque classes already implemented in programming languages:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby deque.py
from collections import deque\n\n# Initialize deque\ndeq: deque[int] = deque()\n\n# Enqueue elements\ndeq.append(2)      # Add to rear\ndeq.append(5)\ndeq.append(4)\ndeq.appendleft(3)  # Add to front\ndeq.appendleft(1)\n\n# Access elements\nfront: int = deq[0]  # Front element\nrear: int = deq[-1]  # Rear element\n\n# Dequeue elements\npop_front: int = deq.popleft()  # Front element dequeue\npop_rear: int = deq.pop()       # Rear element dequeue\n\n# Get deque length\nsize: int = len(deq)\n\n# Check if deque is empty\nis_empty: bool = len(deq) == 0\n
deque.cpp
/* Initialize deque */\ndeque<int> deque;\n\n/* Enqueue elements */\ndeque.push_back(2);   // Add to rear\ndeque.push_back(5);\ndeque.push_back(4);\ndeque.push_front(3);  // Add to front\ndeque.push_front(1);\n\n/* Access elements */\nint front = deque.front(); // Front element\nint back = deque.back();   // Rear element\n\n/* Dequeue elements */\ndeque.pop_front();  // Front element dequeue\ndeque.pop_back();   // Rear element dequeue\n\n/* Get deque length */\nint size = deque.size();\n\n/* Check if deque is empty */\nbool empty = deque.empty();\n
deque.java
/* Initialize deque */\nDeque<Integer> deque = new LinkedList<>();\n\n/* Enqueue elements */\ndeque.offerLast(2);   // Add to rear\ndeque.offerLast(5);\ndeque.offerLast(4);\ndeque.offerFirst(3);  // Add to front\ndeque.offerFirst(1);\n\n/* Access elements */\nint peekFirst = deque.peekFirst();  // Front element\nint peekLast = deque.peekLast();    // Rear element\n\n/* Dequeue elements */\nint popFirst = deque.pollFirst();  // Front element dequeue\nint popLast = deque.pollLast();    // Rear element dequeue\n\n/* Get deque length */\nint size = deque.size();\n\n/* Check if deque is empty */\nboolean isEmpty = deque.isEmpty();\n
deque.cs
/* Initialize deque */\n// In C#, use LinkedList as a deque\nLinkedList<int> deque = new();\n\n/* Enqueue elements */\ndeque.AddLast(2);   // Add to rear\ndeque.AddLast(5);\ndeque.AddLast(4);\ndeque.AddFirst(3);  // Add to front\ndeque.AddFirst(1);\n\n/* Access elements */\nint peekFirst = deque.First.Value;  // Front element\nint peekLast = deque.Last.Value;    // Rear element\n\n/* Dequeue elements */\ndeque.RemoveFirst();  // Front element dequeue\ndeque.RemoveLast();   // Rear element dequeue\n\n/* Get deque length */\nint size = deque.Count;\n\n/* Check if deque is empty */\nbool isEmpty = deque.Count == 0;\n
deque_test.go
/* Initialize deque */\n// In Go, use list as a deque\ndeque := list.New()\n\n/* Enqueue elements */\ndeque.PushBack(2)      // Add to rear\ndeque.PushBack(5)\ndeque.PushBack(4)\ndeque.PushFront(3)     // Add to front\ndeque.PushFront(1)\n\n/* Access elements */\nfront := deque.Front() // Front element\nrear := deque.Back()   // Rear element\n\n/* Dequeue elements */\ndeque.Remove(front)    // Front element dequeue\ndeque.Remove(rear)     // Rear element dequeue\n\n/* Get deque length */\nsize := deque.Len()\n\n/* Check if deque is empty */\nisEmpty := deque.Len() == 0\n
deque.swift
/* Initialize deque */\n// Swift does not have a built-in deque class, can use Array as a deque\nvar deque: [Int] = []\n\n/* Enqueue elements */\ndeque.append(2) // Add to rear\ndeque.append(5)\ndeque.append(4)\ndeque.insert(3, at: 0) // Add to front\ndeque.insert(1, at: 0)\n\n/* Access elements */\nlet peekFirst = deque.first! // Front element\nlet peekLast = deque.last! // Rear element\n\n/* Dequeue elements */\n// When using Array simulation, popFirst has O(n) complexity\nlet popFirst = deque.removeFirst() // Front element dequeue\nlet popLast = deque.removeLast() // Rear element dequeue\n\n/* Get deque length */\nlet size = deque.count\n\n/* Check if deque is empty */\nlet isEmpty = deque.isEmpty\n
deque.js
/* Initialize deque */\n// JavaScript does not have a built-in deque, can only use Array as a deque\nconst deque = [];\n\n/* Enqueue elements */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// Please note that since it's an array, unshift() has O(n) time complexity\ndeque.unshift(3);\ndeque.unshift(1);\n\n/* Access elements */\nconst peekFirst = deque[0];\nconst peekLast = deque[deque.length - 1];\n\n/* Dequeue elements */\n// Please note that since it's an array, shift() has O(n) time complexity\nconst popFront = deque.shift();\nconst popBack = deque.pop();\n\n/* Get deque length */\nconst size = deque.length;\n\n/* Check if deque is empty */\nconst isEmpty = size === 0;\n
deque.ts
/* Initialize deque */\n// TypeScript does not have a built-in deque, can only use Array as a deque\nconst deque: number[] = [];\n\n/* Enqueue elements */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// Please note that since it's an array, unshift() has O(n) time complexity\ndeque.unshift(3);\ndeque.unshift(1);\n\n/* Access elements */\nconst peekFirst: number = deque[0];\nconst peekLast: number = deque[deque.length - 1];\n\n/* Dequeue elements */\n// Please note that since it's an array, shift() has O(n) time complexity\nconst popFront: number = deque.shift() as number;\nconst popBack: number = deque.pop() as number;\n\n/* Get deque length */\nconst size: number = deque.length;\n\n/* Check if deque is empty */\nconst isEmpty: boolean = size === 0;\n
deque.dart
/* Initialize deque */\n// In Dart, Queue is defined as a deque\nQueue<int> deque = Queue<int>();\n\n/* Enqueue elements */\ndeque.addLast(2);  // Add to rear\ndeque.addLast(5);\ndeque.addLast(4);\ndeque.addFirst(3); // Add to front\ndeque.addFirst(1);\n\n/* Access elements */\nint peekFirst = deque.first; // Front element\nint peekLast = deque.last;   // Rear element\n\n/* Dequeue elements */\nint popFirst = deque.removeFirst(); // Front element dequeue\nint popLast = deque.removeLast();   // Rear element dequeue\n\n/* Get deque length */\nint size = deque.length;\n\n/* Check if deque is empty */\nbool isEmpty = deque.isEmpty;\n
deque.rs
/* Initialize deque */\nlet mut deque: VecDeque<u32> = VecDeque::new();\n\n/* Enqueue elements */\ndeque.push_back(2);  // Add to rear\ndeque.push_back(5);\ndeque.push_back(4);\ndeque.push_front(3); // Add to front\ndeque.push_front(1);\n\n/* Access elements */\nif let Some(front) = deque.front() { // Front element\n}\nif let Some(rear) = deque.back() {   // Rear element\n}\n\n/* Dequeue elements */\nif let Some(pop_front) = deque.pop_front() { // Front element dequeue\n}\nif let Some(pop_rear) = deque.pop_back() {   // Rear element dequeue\n}\n\n/* Get deque length */\nlet size = deque.len();\n\n/* Check if deque is empty */\nlet is_empty = deque.is_empty();\n
deque.c
// C does not provide a built-in deque\n
deque.kt
/* Initialize deque */\nval deque = LinkedList<Int>()\n\n/* Enqueue elements */\ndeque.offerLast(2)  // Add to rear\ndeque.offerLast(5)\ndeque.offerLast(4)\ndeque.offerFirst(3) // Add to front\ndeque.offerFirst(1)\n\n/* Access elements */\nval peekFirst = deque.peekFirst() // Front element\nval peekLast = deque.peekLast()   // Rear element\n\n/* Dequeue elements */\nval popFirst = deque.pollFirst() // Front element dequeue\nval popLast = deque.pollLast()   // Rear element dequeue\n\n/* Get deque length */\nval size = deque.size\n\n/* Check if deque is empty */\nval isEmpty = deque.isEmpty()\n
deque.rb
# Initialize deque\n# Ruby does not have a built-in deque, can only use Array as a deque\ndeque = []\n\n# Enqueue elements\ndeque << 2\ndeque << 5\ndeque << 4\n# Please note that since it's an array, Array#unshift has O(n) time complexity\ndeque.unshift(3)\ndeque.unshift(1)\n\n# Access elements\npeek_first = deque.first\npeek_last = deque.last\n\n# Dequeue elements\n# Please note that since it's an array, Array#shift has O(n) time complexity\npop_front = deque.shift\npop_back = deque.pop\n\n# Get deque length\nsize = deque.length\n\n# Check if deque is empty\nis_empty = size.zero?\n
Code Visualization

Full Screen >

","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#532-deque-implementation","level":2,"title":"5.3.2   Deque Implementation *","text":"

The implementation of a deque is similar to that of a queue. You can choose either a linked list or an array as the underlying data structure.

","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#1-doubly-linked-list-implementation","level":3,"title":"1.   Doubly Linked List Implementation","text":"

Reviewing the previous section, we used a regular singly linked list to implement a queue because it conveniently allows deleting the head node (corresponding to dequeue) and adding new nodes after the tail node (corresponding to enqueue).

For a deque, both the front and rear can perform enqueue and dequeue operations. In other words, a deque needs to implement operations in the opposite direction as well. For this reason, we use a \"doubly linked list\" as the underlying data structure for the deque.

As shown in Figure 5-8, we treat the head and tail nodes of the doubly linked list as the front and rear of the deque, implementing functionality to add and remove nodes at both ends.

LinkedListDequepush_last()push_first()pop_last()pop_first()

Figure 5-8   Enqueue and dequeue operations in linked list implementation of deque

The implementation code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_deque.py
class ListNode:\n    \"\"\"Doubly linked list node\"\"\"\n\n    def __init__(self, val: int):\n        \"\"\"Constructor\"\"\"\n        self.val: int = val\n        self.next: ListNode | None = None  # Successor node reference\n        self.prev: ListNode | None = None  # Predecessor node reference\n\nclass LinkedListDeque:\n    \"\"\"Double-ended queue based on doubly linked list implementation\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._front: ListNode | None = None  # Head node front\n        self._rear: ListNode | None = None  # Tail node rear\n        self._size: int = 0  # Length of the double-ended queue\n\n    def size(self) -> int:\n        \"\"\"Get the length of the double-ended queue\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the double-ended queue is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int, is_front: bool):\n        \"\"\"Enqueue operation\"\"\"\n        node = ListNode(num)\n        # If the linked list is empty, make both front and rear point to node\n        if self.is_empty():\n            self._front = self._rear = node\n        # Front of the queue enqueue operation\n        elif is_front:\n            # Add node to the head of the linked list\n            self._front.prev = node\n            node.next = self._front\n            self._front = node  # Update head node\n        # Rear of the queue enqueue operation\n        else:\n            # Add node to the tail of the linked list\n            self._rear.next = node\n            node.prev = self._rear\n            self._rear = node  # Update tail node\n        self._size += 1  # Update queue length\n\n    def push_first(self, num: int):\n        \"\"\"Front of the queue enqueue\"\"\"\n        self.push(num, True)\n\n    def push_last(self, num: int):\n        \"\"\"Rear of the queue enqueue\"\"\"\n        self.push(num, False)\n\n    def pop(self, is_front: bool) -> int:\n        \"\"\"Dequeue operation\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Double-ended queue is empty\")\n        # Front of the queue dequeue operation\n        if is_front:\n            val: int = self._front.val  # Temporarily store head node value\n            # Delete head node\n            fnext: ListNode | None = self._front.next\n            if fnext is not None:\n                fnext.prev = None\n                self._front.next = None\n            self._front = fnext  # Update head node\n        # Rear of the queue dequeue operation\n        else:\n            val: int = self._rear.val  # Temporarily store tail node value\n            # Delete tail node\n            rprev: ListNode | None = self._rear.prev\n            if rprev is not None:\n                rprev.next = None\n                self._rear.prev = None\n            self._rear = rprev  # Update tail node\n        self._size -= 1  # Update queue length\n        return val\n\n    def pop_first(self) -> int:\n        \"\"\"Front of the queue dequeue\"\"\"\n        return self.pop(True)\n\n    def pop_last(self) -> int:\n        \"\"\"Rear of the queue dequeue\"\"\"\n        return self.pop(False)\n\n    def peek_first(self) -> int:\n        \"\"\"Access front of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Double-ended queue is empty\")\n        return self._front.val\n\n    def peek_last(self) -> int:\n        \"\"\"Access rear of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Double-ended queue is empty\")\n        return self._rear.val\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return array for printing\"\"\"\n        node = self._front\n        res = [0] * self.size()\n        for i in range(self.size()):\n            res[i] = node.val\n            node = node.next\n        return res\n
linkedlist_deque.cpp
/* Doubly linked list node */\nstruct DoublyListNode {\n    int val;              // Node value\n    DoublyListNode *next; // Successor node pointer\n    DoublyListNode *prev; // Predecessor node pointer\n    DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {\n    }\n};\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n  private:\n    DoublyListNode *front, *rear; // Head node front, tail node rear\n    int queSize = 0;              // Length of the double-ended queue\n\n  public:\n    /* Constructor */\n    LinkedListDeque() : front(nullptr), rear(nullptr) {\n    }\n\n    /* Destructor */\n    ~LinkedListDeque() {\n        // Traverse linked list to delete nodes and free memory\n        DoublyListNode *pre, *cur = front;\n        while (cur != nullptr) {\n            pre = cur;\n            cur = cur->next;\n            delete pre;\n        }\n    }\n\n    /* Get the length of the double-ended queue */\n    int size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue operation */\n    void push(int num, bool isFront) {\n        DoublyListNode *node = new DoublyListNode(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty())\n            front = rear = node;\n        // Front of the queue enqueue operation\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front->prev = node;\n            node->next = front;\n            front = node; // Update head node\n        // Rear of the queue enqueue operation\n        } else {\n            // Add node to the tail of the linked list\n            rear->next = node;\n            node->prev = rear;\n            rear = node; // Update tail node\n        }\n        queSize++; // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    void pushFirst(int num) {\n        push(num, true);\n    }\n\n    /* Rear of the queue enqueue */\n    void pushLast(int num) {\n        push(num, false);\n    }\n\n    /* Dequeue operation */\n    int pop(bool isFront) {\n        if (isEmpty())\n            throw out_of_range(\"Queue is empty\");\n        int val;\n        // Temporarily store head node value\n        if (isFront) {\n            val = front->val; // Delete head node\n            // Delete head node\n            DoublyListNode *fNext = front->next;\n            if (fNext != nullptr) {\n                fNext->prev = nullptr;\n                front->next = nullptr;\n            }\n            delete front;\n            front = fNext; // Update head node\n        // Temporarily store tail node value\n        } else {\n            val = rear->val; // Delete tail node\n            // Update tail node\n            DoublyListNode *rPrev = rear->prev;\n            if (rPrev != nullptr) {\n                rPrev->next = nullptr;\n                rear->prev = nullptr;\n            }\n            delete rear;\n            rear = rPrev; // Update tail node\n        }\n        queSize--; // Update queue length\n        return val;\n    }\n\n    /* Rear of the queue dequeue */\n    int popFirst() {\n        return pop(true);\n    }\n\n    /* Access rear of the queue element */\n    int popLast() {\n        return pop(false);\n    }\n\n    /* Return list for printing */\n    int peekFirst() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return front->val;\n    }\n\n    /* Driver Code */\n    int peekLast() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return rear->val;\n    }\n\n    /* Return array for printing */\n    vector<int> toVector() {\n        DoublyListNode *node = front;\n        vector<int> res(size());\n        for (int i = 0; i < res.size(); i++) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
linkedlist_deque.java
/* Doubly linked list node */\nclass ListNode {\n    int val; // Node value\n    ListNode next; // Successor node reference\n    ListNode prev; // Predecessor node reference\n\n    ListNode(int val) {\n        this.val = val;\n        prev = next = null;\n    }\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    private ListNode front, rear; // Head node front, tail node rear\n    private int queSize = 0; // Length of the double-ended queue\n\n    public LinkedListDeque() {\n        front = rear = null;\n    }\n\n    /* Get the length of the double-ended queue */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue operation */\n    private void push(int num, boolean isFront) {\n        ListNode node = new ListNode(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty())\n            front = rear = node;\n        // Front of the queue enqueue operation\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front.prev = node;\n            node.next = front;\n            front = node; // Update head node\n        // Rear of the queue enqueue operation\n        } else {\n            // Add node to the tail of the linked list\n            rear.next = node;\n            node.prev = rear;\n            rear = node; // Update tail node\n        }\n        queSize++; // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    public void pushFirst(int num) {\n        push(num, true);\n    }\n\n    /* Rear of the queue enqueue */\n    public void pushLast(int num) {\n        push(num, false);\n    }\n\n    /* Dequeue operation */\n    private int pop(boolean isFront) {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        int val;\n        // Temporarily store head node value\n        if (isFront) {\n            val = front.val; // Delete head node\n            // Delete head node\n            ListNode fNext = front.next;\n            if (fNext != null) {\n                fNext.prev = null;\n                front.next = null;\n            }\n            front = fNext; // Update head node\n        // Temporarily store tail node value\n        } else {\n            val = rear.val; // Delete tail node\n            // Update tail node\n            ListNode rPrev = rear.prev;\n            if (rPrev != null) {\n                rPrev.next = null;\n                rear.prev = null;\n            }\n            rear = rPrev; // Update tail node\n        }\n        queSize--; // Update queue length\n        return val;\n    }\n\n    /* Rear of the queue dequeue */\n    public int popFirst() {\n        return pop(true);\n    }\n\n    /* Access rear of the queue element */\n    public int popLast() {\n        return pop(false);\n    }\n\n    /* Return list for printing */\n    public int peekFirst() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return front.val;\n    }\n\n    /* Driver Code */\n    public int peekLast() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return rear.val;\n    }\n\n    /* Return array for printing */\n    public int[] toArray() {\n        ListNode node = front;\n        int[] res = new int[size()];\n        for (int i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_deque.cs
/* Doubly linked list node */\nclass ListNode(int val) {\n    public int val = val;       // Node value\n    public ListNode? next = null; // Successor node reference\n    public ListNode? prev = null; // Predecessor node reference\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    ListNode? front, rear; // Head node front, tail node rear\n    int queSize = 0;      // Length of the double-ended queue\n\n    public LinkedListDeque() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the length of the double-ended queue */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Enqueue operation */\n    void Push(int num, bool isFront) {\n        ListNode node = new(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (IsEmpty()) {\n            front = node;\n            rear = node;\n        }\n        // Front of the queue enqueue operation\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front!.prev = node;\n            node.next = front;\n            front = node; // Update head node\n        }\n        // Rear of the queue enqueue operation\n        else {\n            // Add node to the tail of the linked list\n            rear!.next = node;\n            node.prev = rear;\n            rear = node;  // Update tail node\n        }\n\n        queSize++; // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    public void PushFirst(int num) {\n        Push(num, true);\n    }\n\n    /* Rear of the queue enqueue */\n    public void PushLast(int num) {\n        Push(num, false);\n    }\n\n    /* Dequeue operation */\n    int? Pop(bool isFront) {\n        if (IsEmpty())\n            throw new Exception();\n        int? val;\n        // Temporarily store head node value\n        if (isFront) {\n            val = front?.val; // Delete head node\n            // Delete head node\n            ListNode? fNext = front?.next;\n            if (fNext != null) {\n                fNext.prev = null;\n                front!.next = null;\n            }\n            front = fNext;   // Update head node\n        }\n        // Temporarily store tail node value\n        else {\n            val = rear?.val;  // Delete tail node\n            // Update tail node\n            ListNode? rPrev = rear?.prev;\n            if (rPrev != null) {\n                rPrev.next = null;\n                rear!.prev = null;\n            }\n            rear = rPrev;    // Update tail node\n        }\n\n        queSize--; // Update queue length\n        return val;\n    }\n\n    /* Rear of the queue dequeue */\n    public int? PopFirst() {\n        return Pop(true);\n    }\n\n    /* Access rear of the queue element */\n    public int? PopLast() {\n        return Pop(false);\n    }\n\n    /* Return list for printing */\n    public int? PeekFirst() {\n        if (IsEmpty())\n            throw new Exception();\n        return front?.val;\n    }\n\n    /* Driver Code */\n    public int? PeekLast() {\n        if (IsEmpty())\n            throw new Exception();\n        return rear?.val;\n    }\n\n    /* Return array for printing */\n    public int?[] ToArray() {\n        ListNode? node = front;\n        int?[] res = new int?[Size()];\n        for (int i = 0; i < res.Length; i++) {\n            res[i] = node?.val;\n            node = node?.next;\n        }\n\n        return res;\n    }\n}\n
linkedlist_deque.go
/* Double-ended queue based on doubly linked list implementation */\ntype linkedListDeque struct {\n    // Use built-in package list\n    data *list.List\n}\n\n/* Initialize deque */\nfunc newLinkedListDeque() *linkedListDeque {\n    return &linkedListDeque{\n        data: list.New(),\n    }\n}\n\n/* Front element enqueue */\nfunc (s *linkedListDeque) pushFirst(value any) {\n    s.data.PushFront(value)\n}\n\n/* Rear element enqueue */\nfunc (s *linkedListDeque) pushLast(value any) {\n    s.data.PushBack(value)\n}\n\n/* Check if the double-ended queue is empty */\nfunc (s *linkedListDeque) popFirst() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Rear element dequeue */\nfunc (s *linkedListDeque) popLast() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Return list for printing */\nfunc (s *linkedListDeque) peekFirst() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    return e.Value\n}\n\n/* Driver Code */\nfunc (s *linkedListDeque) peekLast() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    return e.Value\n}\n\n/* Get the length of the queue */\nfunc (s *linkedListDeque) size() int {\n    return s.data.Len()\n}\n\n/* Check if the queue is empty */\nfunc (s *linkedListDeque) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get List for printing */\nfunc (s *linkedListDeque) toList() *list.List {\n    return s.data\n}\n
linkedlist_deque.swift
/* Doubly linked list node */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Successor node reference\n    weak var prev: ListNode? // Predecessor node reference\n\n    init(val: Int) {\n        self.val = val\n    }\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    private var front: ListNode? // Head node front\n    private var rear: ListNode? // Tail node rear\n    private var _size: Int // Length of the double-ended queue\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the length of the double-ended queue */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check if the double-ended queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue operation */\n    private func push(num: Int, isFront: Bool) {\n        let node = ListNode(val: num)\n        // If the linked list is empty, make both front and rear point to node\n        if isEmpty() {\n            front = node\n            rear = node\n        }\n        // Front of the queue enqueue operation\n        else if isFront {\n            // Add node to the head of the linked list\n            front?.prev = node\n            node.next = front\n            front = node // Update head node\n        }\n        // Rear of the queue enqueue operation\n        else {\n            // Add node to the tail of the linked list\n            rear?.next = node\n            node.prev = rear\n            rear = node // Update tail node\n        }\n        _size += 1 // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    func pushFirst(num: Int) {\n        push(num: num, isFront: true)\n    }\n\n    /* Rear of the queue enqueue */\n    func pushLast(num: Int) {\n        push(num: num, isFront: false)\n    }\n\n    /* Dequeue operation */\n    private func pop(isFront: Bool) -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        let val: Int\n        // Temporarily store head node value\n        if isFront {\n            val = front!.val // Delete head node\n            // Delete head node\n            let fNext = front?.next\n            if fNext != nil {\n                fNext?.prev = nil\n                front?.next = nil\n            }\n            front = fNext // Update head node\n        }\n        // Temporarily store tail node value\n        else {\n            val = rear!.val // Delete tail node\n            // Update tail node\n            let rPrev = rear?.prev\n            if rPrev != nil {\n                rPrev?.next = nil\n                rear?.prev = nil\n            }\n            rear = rPrev // Update tail node\n        }\n        _size -= 1 // Update queue length\n        return val\n    }\n\n    /* Rear of the queue dequeue */\n    func popFirst() -> Int {\n        pop(isFront: true)\n    }\n\n    /* Access rear of the queue element */\n    func popLast() -> Int {\n        pop(isFront: false)\n    }\n\n    /* Return list for printing */\n    func peekFirst() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return front!.val\n    }\n\n    /* Driver Code */\n    func peekLast() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return rear!.val\n    }\n\n    /* Return array for printing */\n    func toArray() -> [Int] {\n        var node = front\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
linkedlist_deque.js
/* Doubly linked list node */\nclass ListNode {\n    prev; // Predecessor node reference (pointer)\n    next; // Successor node reference (pointer)\n    val; // Node value\n\n    constructor(val) {\n        this.val = val;\n        this.next = null;\n        this.prev = null;\n    }\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    #front; // Head node front\n    #rear; // Tail node rear\n    #queSize; // Length of the double-ended queue\n\n    constructor() {\n        this.#front = null;\n        this.#rear = null;\n        this.#queSize = 0;\n    }\n\n    /* Rear of the queue enqueue operation */\n    pushLast(val) {\n        const node = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.#queSize === 0) {\n            this.#front = node;\n            this.#rear = node;\n        } else {\n            // Add node to the tail of the linked list\n            this.#rear.next = node;\n            node.prev = this.#rear;\n            this.#rear = node; // Update tail node\n        }\n        this.#queSize++;\n    }\n\n    /* Front of the queue enqueue operation */\n    pushFirst(val) {\n        const node = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.#queSize === 0) {\n            this.#front = node;\n            this.#rear = node;\n        } else {\n            // Add node to the head of the linked list\n            this.#front.prev = node;\n            node.next = this.#front;\n            this.#front = node; // Update head node\n        }\n        this.#queSize++;\n    }\n\n    /* Temporarily store tail node value */\n    popLast() {\n        if (this.#queSize === 0) {\n            return null;\n        }\n        const value = this.#rear.val; // Store tail node value\n        // Update tail node\n        let temp = this.#rear.prev;\n        if (temp !== null) {\n            temp.next = null;\n            this.#rear.prev = null;\n        }\n        this.#rear = temp; // Update tail node\n        this.#queSize--;\n        return value;\n    }\n\n    /* Temporarily store head node value */\n    popFirst() {\n        if (this.#queSize === 0) {\n            return null;\n        }\n        const value = this.#front.val; // Store tail node value\n        // Delete head node\n        let temp = this.#front.next;\n        if (temp !== null) {\n            temp.prev = null;\n            this.#front.next = null;\n        }\n        this.#front = temp; // Update head node\n        this.#queSize--;\n        return value;\n    }\n\n    /* Driver Code */\n    peekLast() {\n        return this.#queSize === 0 ? null : this.#rear.val;\n    }\n\n    /* Return list for printing */\n    peekFirst() {\n        return this.#queSize === 0 ? null : this.#front.val;\n    }\n\n    /* Get the length of the double-ended queue */\n    size() {\n        return this.#queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Print deque */\n    print() {\n        const arr = [];\n        let temp = this.#front;\n        while (temp !== null) {\n            arr.push(temp.val);\n            temp = temp.next;\n        }\n        console.log('[' + arr.join(', ') + ']');\n    }\n}\n
linkedlist_deque.ts
/* Doubly linked list node */\nclass ListNode {\n    prev: ListNode; // Predecessor node reference (pointer)\n    next: ListNode; // Successor node reference (pointer)\n    val: number; // Node value\n\n    constructor(val: number) {\n        this.val = val;\n        this.next = null;\n        this.prev = null;\n    }\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    private front: ListNode; // Head node front\n    private rear: ListNode; // Tail node rear\n    private queSize: number; // Length of the double-ended queue\n\n    constructor() {\n        this.front = null;\n        this.rear = null;\n        this.queSize = 0;\n    }\n\n    /* Rear of the queue enqueue operation */\n    pushLast(val: number): void {\n        const node: ListNode = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.queSize === 0) {\n            this.front = node;\n            this.rear = node;\n        } else {\n            // Add node to the tail of the linked list\n            this.rear.next = node;\n            node.prev = this.rear;\n            this.rear = node; // Update tail node\n        }\n        this.queSize++;\n    }\n\n    /* Front of the queue enqueue operation */\n    pushFirst(val: number): void {\n        const node: ListNode = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.queSize === 0) {\n            this.front = node;\n            this.rear = node;\n        } else {\n            // Add node to the head of the linked list\n            this.front.prev = node;\n            node.next = this.front;\n            this.front = node; // Update head node\n        }\n        this.queSize++;\n    }\n\n    /* Temporarily store tail node value */\n    popLast(): number {\n        if (this.queSize === 0) {\n            return null;\n        }\n        const value: number = this.rear.val; // Store tail node value\n        // Update tail node\n        let temp: ListNode = this.rear.prev;\n        if (temp !== null) {\n            temp.next = null;\n            this.rear.prev = null;\n        }\n        this.rear = temp; // Update tail node\n        this.queSize--;\n        return value;\n    }\n\n    /* Temporarily store head node value */\n    popFirst(): number {\n        if (this.queSize === 0) {\n            return null;\n        }\n        const value: number = this.front.val; // Store tail node value\n        // Delete head node\n        let temp: ListNode = this.front.next;\n        if (temp !== null) {\n            temp.prev = null;\n            this.front.next = null;\n        }\n        this.front = temp; // Update head node\n        this.queSize--;\n        return value;\n    }\n\n    /* Driver Code */\n    peekLast(): number {\n        return this.queSize === 0 ? null : this.rear.val;\n    }\n\n    /* Return list for printing */\n    peekFirst(): number {\n        return this.queSize === 0 ? null : this.front.val;\n    }\n\n    /* Get the length of the double-ended queue */\n    size(): number {\n        return this.queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Print deque */\n    print(): void {\n        const arr: number[] = [];\n        let temp: ListNode = this.front;\n        while (temp !== null) {\n            arr.push(temp.val);\n            temp = temp.next;\n        }\n        console.log('[' + arr.join(', ') + ']');\n    }\n}\n
linkedlist_deque.dart
/* Doubly linked list node */\nclass ListNode {\n  int val; // Node value\n  ListNode? next; // Successor node reference\n  ListNode? prev; // Predecessor node reference\n\n  ListNode(this.val, {this.next, this.prev});\n}\n\n/* Deque implemented based on doubly linked list */\nclass LinkedListDeque {\n  late ListNode? _front; // Head node _front\n  late ListNode? _rear; // Tail node _rear\n  int _queSize = 0; // Length of the double-ended queue\n\n  LinkedListDeque() {\n    this._front = null;\n    this._rear = null;\n  }\n\n  /* Get deque length */\n  int size() {\n    return this._queSize;\n  }\n\n  /* Check if the double-ended queue is empty */\n  bool isEmpty() {\n    return size() == 0;\n  }\n\n  /* Enqueue operation */\n  void push(int _num, bool isFront) {\n    final ListNode node = ListNode(_num);\n    if (isEmpty()) {\n      // If list is empty, let both _front and _rear point to node\n      _front = _rear = node;\n    } else if (isFront) {\n      // Front of the queue enqueue operation\n      // Add node to the head of the linked list\n      _front!.prev = node;\n      node.next = _front;\n      _front = node; // Update head node\n    } else {\n      // Rear of the queue enqueue operation\n      // Add node to the tail of the linked list\n      _rear!.next = node;\n      node.prev = _rear;\n      _rear = node; // Update tail node\n    }\n    _queSize++; // Update queue length\n  }\n\n  /* Front of the queue enqueue */\n  void pushFirst(int _num) {\n    push(_num, true);\n  }\n\n  /* Rear of the queue enqueue */\n  void pushLast(int _num) {\n    push(_num, false);\n  }\n\n  /* Dequeue operation */\n  int? pop(bool isFront) {\n    // If queue is empty, return null directly\n    if (isEmpty()) {\n      return null;\n    }\n    final int val;\n    if (isFront) {\n      // Temporarily store head node value\n      val = _front!.val; // Delete head node\n      // Delete head node\n      ListNode? fNext = _front!.next;\n      if (fNext != null) {\n        fNext.prev = null;\n        _front!.next = null;\n      }\n      _front = fNext; // Update head node\n    } else {\n      // Temporarily store tail node value\n      val = _rear!.val; // Delete tail node\n      // Update tail node\n      ListNode? rPrev = _rear!.prev;\n      if (rPrev != null) {\n        rPrev.next = null;\n        _rear!.prev = null;\n      }\n      _rear = rPrev; // Update tail node\n    }\n    _queSize--; // Update queue length\n    return val;\n  }\n\n  /* Rear of the queue dequeue */\n  int? popFirst() {\n    return pop(true);\n  }\n\n  /* Access rear of the queue element */\n  int? popLast() {\n    return pop(false);\n  }\n\n  /* Return list for printing */\n  int? peekFirst() {\n    return _front?.val;\n  }\n\n  /* Driver Code */\n  int? peekLast() {\n    return _rear?.val;\n  }\n\n  /* Return array for printing */\n  List<int> toArray() {\n    ListNode? node = _front;\n    final List<int> res = [];\n    for (int i = 0; i < _queSize; i++) {\n      res.add(node!.val);\n      node = node.next;\n    }\n    return res;\n  }\n}\n
linkedlist_deque.rs
/* Doubly linked list node */\npub struct ListNode<T> {\n    pub val: T,                                 // Node value\n    pub next: Option<Rc<RefCell<ListNode<T>>>>, // Successor node pointer\n    pub prev: Option<Rc<RefCell<ListNode<T>>>>, // Predecessor node pointer\n}\n\nimpl<T> ListNode<T> {\n    pub fn new(val: T) -> Rc<RefCell<ListNode<T>>> {\n        Rc::new(RefCell::new(ListNode {\n            val,\n            next: None,\n            prev: None,\n        }))\n    }\n}\n\n/* Double-ended queue based on doubly linked list implementation */\n#[allow(dead_code)]\npub struct LinkedListDeque<T> {\n    front: Option<Rc<RefCell<ListNode<T>>>>, // Head node front\n    rear: Option<Rc<RefCell<ListNode<T>>>>,  // Tail node rear\n    que_size: usize,                         // Length of the double-ended queue\n}\n\nimpl<T: Copy> LinkedListDeque<T> {\n    pub fn new() -> Self {\n        Self {\n            front: None,\n            rear: None,\n            que_size: 0,\n        }\n    }\n\n    /* Get the length of the double-ended queue */\n    pub fn size(&self) -> usize {\n        return self.que_size;\n    }\n\n    /* Check if the double-ended queue is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.que_size == 0;\n    }\n\n    /* Enqueue operation */\n    fn push(&mut self, num: T, is_front: bool) {\n        let node = ListNode::new(num);\n        // Front of the queue enqueue operation\n        if is_front {\n            match self.front.take() {\n                // If the linked list is empty, make both front and rear point to node\n                None => {\n                    self.rear = Some(node.clone());\n                    self.front = Some(node);\n                }\n                // Add node to the head of the linked list\n                Some(old_front) => {\n                    old_front.borrow_mut().prev = Some(node.clone());\n                    node.borrow_mut().next = Some(old_front);\n                    self.front = Some(node); // Update head node\n                }\n            }\n        }\n        // Rear of the queue enqueue operation\n        else {\n            match self.rear.take() {\n                // If the linked list is empty, make both front and rear point to node\n                None => {\n                    self.front = Some(node.clone());\n                    self.rear = Some(node);\n                }\n                // Add node to the tail of the linked list\n                Some(old_rear) => {\n                    old_rear.borrow_mut().next = Some(node.clone());\n                    node.borrow_mut().prev = Some(old_rear);\n                    self.rear = Some(node); // Update tail node\n                }\n            }\n        }\n        self.que_size += 1; // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    pub fn push_first(&mut self, num: T) {\n        self.push(num, true);\n    }\n\n    /* Rear of the queue enqueue */\n    pub fn push_last(&mut self, num: T) {\n        self.push(num, false);\n    }\n\n    /* Dequeue operation */\n    fn pop(&mut self, is_front: bool) -> Option<T> {\n        // If queue is empty, return None directly\n        if self.is_empty() {\n            return None;\n        };\n        // Temporarily store head node value\n        if is_front {\n            self.front.take().map(|old_front| {\n                match old_front.borrow_mut().next.take() {\n                    Some(new_front) => {\n                        new_front.borrow_mut().prev.take();\n                        self.front = Some(new_front); // Update head node\n                    }\n                    None => {\n                        self.rear.take();\n                    }\n                }\n                self.que_size -= 1; // Update queue length\n                old_front.borrow().val\n            })\n        }\n        // Temporarily store tail node value\n        else {\n            self.rear.take().map(|old_rear| {\n                match old_rear.borrow_mut().prev.take() {\n                    Some(new_rear) => {\n                        new_rear.borrow_mut().next.take();\n                        self.rear = Some(new_rear); // Update tail node\n                    }\n                    None => {\n                        self.front.take();\n                    }\n                }\n                self.que_size -= 1; // Update queue length\n                old_rear.borrow().val\n            })\n        }\n    }\n\n    /* Rear of the queue dequeue */\n    pub fn pop_first(&mut self) -> Option<T> {\n        return self.pop(true);\n    }\n\n    /* Access rear of the queue element */\n    pub fn pop_last(&mut self) -> Option<T> {\n        return self.pop(false);\n    }\n\n    /* Return list for printing */\n    pub fn peek_first(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.front.as_ref()\n    }\n\n    /* Driver Code */\n    pub fn peek_last(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.rear.as_ref()\n    }\n\n    /* Return array for printing */\n    pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n        let mut res: Vec<T> = Vec::new();\n        fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {\n            if let Some(cur) = cur {\n                res.push(cur.borrow().val);\n                recur(cur.borrow().next.as_ref(), res);\n            }\n        }\n\n        recur(head, &mut res);\n        res\n    }\n}\n
linkedlist_deque.c
/* Doubly linked list node */\ntypedef struct DoublyListNode {\n    int val;                     // Node value\n    struct DoublyListNode *next; // Successor node\n    struct DoublyListNode *prev; // Predecessor node\n} DoublyListNode;\n\n/* Constructor */\nDoublyListNode *newDoublyListNode(int num) {\n    DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode));\n    new->val = num;\n    new->next = NULL;\n    new->prev = NULL;\n    return new;\n}\n\n/* Destructor */\nvoid delDoublyListNode(DoublyListNode *node) {\n    free(node);\n}\n\n/* Double-ended queue based on doubly linked list implementation */\ntypedef struct {\n    DoublyListNode *front, *rear; // Head node front, tail node rear\n    int queSize;                  // Length of the double-ended queue\n} LinkedListDeque;\n\n/* Constructor */\nLinkedListDeque *newLinkedListDeque() {\n    LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque));\n    deque->front = NULL;\n    deque->rear = NULL;\n    deque->queSize = 0;\n    return deque;\n}\n\n/* Destructor */\nvoid delLinkedListdeque(LinkedListDeque *deque) {\n    // Free all nodes\n    for (int i = 0; i < deque->queSize && deque->front != NULL; i++) {\n        DoublyListNode *tmp = deque->front;\n        deque->front = deque->front->next;\n        free(tmp);\n    }\n    // Free deque structure\n    free(deque);\n}\n\n/* Get the length of the queue */\nint size(LinkedListDeque *deque) {\n    return deque->queSize;\n}\n\n/* Check if the queue is empty */\nbool empty(LinkedListDeque *deque) {\n    return (size(deque) == 0);\n}\n\n/* Enqueue */\nvoid push(LinkedListDeque *deque, int num, bool isFront) {\n    DoublyListNode *node = newDoublyListNode(num);\n    // If list is empty, set both front and rear to node\n    if (empty(deque)) {\n        deque->front = deque->rear = node;\n    }\n    // Front of the queue enqueue operation\n    else if (isFront) {\n        // Add node to the head of the linked list\n        deque->front->prev = node;\n        node->next = deque->front;\n        deque->front = node; // Update head node\n    }\n    // Rear of the queue enqueue operation\n    else {\n        // Add node to the tail of the linked list\n        deque->rear->next = node;\n        node->prev = deque->rear;\n        deque->rear = node;\n    }\n    deque->queSize++; // Update queue length\n}\n\n/* Front of the queue enqueue */\nvoid pushFirst(LinkedListDeque *deque, int num) {\n    push(deque, num, true);\n}\n\n/* Rear of the queue enqueue */\nvoid pushLast(LinkedListDeque *deque, int num) {\n    push(deque, num, false);\n}\n\n/* Return list for printing */\nint peekFirst(LinkedListDeque *deque) {\n    assert(size(deque) && deque->front);\n    return deque->front->val;\n}\n\n/* Driver Code */\nint peekLast(LinkedListDeque *deque) {\n    assert(size(deque) && deque->rear);\n    return deque->rear->val;\n}\n\n/* Dequeue */\nint pop(LinkedListDeque *deque, bool isFront) {\n    if (empty(deque))\n        return -1;\n    int val;\n    // Temporarily store head node value\n    if (isFront) {\n        val = peekFirst(deque); // Delete head node\n        DoublyListNode *fNext = deque->front->next;\n        if (fNext) {\n            fNext->prev = NULL;\n            deque->front->next = NULL;\n        }\n        delDoublyListNode(deque->front);\n        deque->front = fNext; // Update head node\n    }\n    // Temporarily store tail node value\n    else {\n        val = peekLast(deque); // Delete tail node\n        DoublyListNode *rPrev = deque->rear->prev;\n        if (rPrev) {\n            rPrev->next = NULL;\n            deque->rear->prev = NULL;\n        }\n        delDoublyListNode(deque->rear);\n        deque->rear = rPrev; // Update tail node\n    }\n    deque->queSize--; // Update queue length\n    return val;\n}\n\n/* Rear of the queue dequeue */\nint popFirst(LinkedListDeque *deque) {\n    return pop(deque, true);\n}\n\n/* Access rear of the queue element */\nint popLast(LinkedListDeque *deque) {\n    return pop(deque, false);\n}\n\n/* Print queue */\nvoid printLinkedListDeque(LinkedListDeque *deque) {\n    int *arr = malloc(sizeof(int) * deque->queSize);\n    // Copy data from list to array\n    int i;\n    DoublyListNode *node;\n    for (i = 0, node = deque->front; i < deque->queSize; i++) {\n        arr[i] = node->val;\n        node = node->next;\n    }\n    printArray(arr, deque->queSize);\n    free(arr);\n}\n
linkedlist_deque.kt
/* Doubly linked list node */\nclass ListNode(var _val: Int) {\n    // Node value\n    var next: ListNode? = null // Successor node reference\n    var prev: ListNode? = null // Predecessor node reference\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    private var front: ListNode? = null // Head node front\n    private var rear: ListNode? = null // Tail node rear\n    private var queSize: Int = 0 // Length of the double-ended queue\n\n    /* Get the length of the double-ended queue */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check if the double-ended queue is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Enqueue operation */\n    fun push(num: Int, isFront: Boolean) {\n        val node = ListNode(num)\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty()) {\n            rear = node\n            front = rear\n            // Front of the queue enqueue operation\n        } else if (isFront) {\n            // Add node to the head of the linked list\n            front?.prev = node\n            node.next = front\n            front = node // Update head node\n            // Rear of the queue enqueue operation\n        } else {\n            // Add node to the tail of the linked list\n            rear?.next = node\n            node.prev = rear\n            rear = node // Update tail node\n        }\n        queSize++ // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    fun pushFirst(num: Int) {\n        push(num, true)\n    }\n\n    /* Rear of the queue enqueue */\n    fun pushLast(num: Int) {\n        push(num, false)\n    }\n\n    /* Dequeue operation */\n    fun pop(isFront: Boolean): Int {\n        if (isEmpty()) \n            throw IndexOutOfBoundsException()\n        val _val: Int\n        // Temporarily store head node value\n        if (isFront) {\n            _val = front!!._val // Delete head node\n            // Delete head node\n            val fNext = front!!.next\n            if (fNext != null) {\n                fNext.prev = null\n                front!!.next = null\n            }\n            front = fNext // Update head node\n            // Temporarily store tail node value\n        } else {\n            _val = rear!!._val // Delete tail node\n            // Update tail node\n            val rPrev = rear!!.prev\n            if (rPrev != null) {\n                rPrev.next = null\n                rear!!.prev = null\n            }\n            rear = rPrev // Update tail node\n        }\n        queSize-- // Update queue length\n        return _val\n    }\n\n    /* Rear of the queue dequeue */\n    fun popFirst(): Int {\n        return pop(true)\n    }\n\n    /* Access rear of the queue element */\n    fun popLast(): Int {\n        return pop(false)\n    }\n\n    /* Return list for printing */\n    fun peekFirst(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return front!!._val\n    }\n\n    /* Driver Code */\n    fun peekLast(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return rear!!._val\n    }\n\n    /* Return array for printing */\n    fun toArray(): IntArray {\n        var node = front\n        val res = IntArray(size())\n        for (i in res.indices) {\n            res[i] = node!!._val\n            node = node.next\n        }\n        return res\n    }\n}\n
linkedlist_deque.rb
=begin\nFile: linkedlist_deque.rb\nCreated Time: 2024-04-06\nAuthor: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)\n=end\n\n### Doubly linked list node\nclass ListNode\n  attr_accessor :val\n  attr_accessor :next # Successor node reference\n  attr_accessor :prev # Predecessor node reference\n\n  ### Constructor ###\n  def initialize(val)\n    @val = val\n  end\nend\n\n### Deque based on doubly linked list ###\nclass LinkedListDeque\n  ### Get deque length ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @front = nil  # Head node front\n    @rear = nil   # Tail node rear\n    @size = 0     # Length of the double-ended queue\n  end\n\n  ### Check if deque is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue operation ###\n  def push(num, is_front)\n    node = ListNode.new(num)\n    # If list is empty, set both front and rear to node\n    if is_empty?\n      @front = @rear = node\n    # Front of the queue enqueue operation\n    elsif is_front\n      # Add node to the head of the linked list\n      @front.prev = node\n      node.next = @front\n      @front = node # Update head node\n    # Rear of the queue enqueue operation\n    else\n      # Add node to the tail of the linked list\n      @rear.next = node\n      node.prev = @rear\n      @rear = node # Update tail node\n    end\n    @size += 1 # Update queue length\n  end\n\n  ### Enqueue at front ###\n  def push_first(num)\n    push(num, true)\n  end\n\n  ### Enqueue at rear ###\n  def push_last(num)\n    push(num, false)\n  end\n\n  ### Dequeue operation ###\n  def pop(is_front)\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    # Temporarily store head node value\n    if is_front\n      val = @front.val # Delete head node\n      # Delete head node\n      fnext = @front.next\n      unless fnext.nil?\n        fnext.prev = nil\n        @front.next = nil\n      end\n      @front = fnext # Update head node\n    # Temporarily store tail node value\n    else\n      val = @rear.val # Delete tail node\n      # Update tail node\n      rprev = @rear.prev\n      unless rprev.nil?\n        rprev.next = nil\n        @rear.prev = nil\n      end\n      @rear = rprev # Update tail node\n    end\n    @size -= 1 # Update queue length\n\n    val\n  end\n\n  ### Dequeue from front ###\n  def pop_first\n    pop(true)\n  end\n\n  ### Dequeue from front ###\n  def pop_last\n    pop(false)\n  end\n\n  ### Access front element ###\n  def peek_first\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @front.val\n  end\n\n  ### Access rear element ###\n  def peek_last\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @rear.val\n  end\n\n  ### Return array for printing ###\n  def to_array\n    node = @front\n    res = Array.new(size, 0)\n    for i in 0...size\n      res[i] = node.val\n      node = node.next\n    end\n    res\n  end\nend\n
","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#2-array-implementation","level":3,"title":"2.   Array Implementation","text":"

As shown in Figure 5-9, similar to implementing a queue based on an array, we can also use a circular array to implement a deque.

ArrayDequepush_last()push_first()pop_last()pop_first()

Figure 5-9   Enqueue and dequeue operations in array implementation of deque

Based on the queue implementation, we only need to add methods for \"enqueue at front\" and \"dequeue from rear\":

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_deque.py
class ArrayDeque:\n    \"\"\"Double-ended queue based on circular array implementation\"\"\"\n\n    def __init__(self, capacity: int):\n        \"\"\"Constructor\"\"\"\n        self._nums: list[int] = [0] * capacity\n        self._front: int = 0\n        self._size: int = 0\n\n    def capacity(self) -> int:\n        \"\"\"Get the capacity of the double-ended queue\"\"\"\n        return len(self._nums)\n\n    def size(self) -> int:\n        \"\"\"Get the length of the double-ended queue\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the double-ended queue is empty\"\"\"\n        return self._size == 0\n\n    def index(self, i: int) -> int:\n        \"\"\"Calculate circular array index\"\"\"\n        # Use modulo operation to wrap the array head and tail together\n        # When i passes the tail of the array, return to the head\n        # When i passes the head of the array, return to the tail\n        return (i + self.capacity()) % self.capacity()\n\n    def push_first(self, num: int):\n        \"\"\"Front of the queue enqueue\"\"\"\n        if self._size == self.capacity():\n            print(\"Double-ended queue is full\")\n            return\n        # Front pointer moves one position to the left\n        # Use modulo operation to wrap front around to the tail after passing the head of the array\n        self._front = self.index(self._front - 1)\n        # Add num to the front of the queue\n        self._nums[self._front] = num\n        self._size += 1\n\n    def push_last(self, num: int):\n        \"\"\"Rear of the queue enqueue\"\"\"\n        if self._size == self.capacity():\n            print(\"Double-ended queue is full\")\n            return\n        # Calculate rear pointer, points to rear index + 1\n        rear = self.index(self._front + self._size)\n        # Add num to the rear of the queue\n        self._nums[rear] = num\n        self._size += 1\n\n    def pop_first(self) -> int:\n        \"\"\"Front of the queue dequeue\"\"\"\n        num = self.peek_first()\n        # Front pointer moves one position backward\n        self._front = self.index(self._front + 1)\n        self._size -= 1\n        return num\n\n    def pop_last(self) -> int:\n        \"\"\"Rear of the queue dequeue\"\"\"\n        num = self.peek_last()\n        self._size -= 1\n        return num\n\n    def peek_first(self) -> int:\n        \"\"\"Access front of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Double-ended queue is empty\")\n        return self._nums[self._front]\n\n    def peek_last(self) -> int:\n        \"\"\"Access rear of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Double-ended queue is empty\")\n        # Calculate tail element index\n        last = self.index(self._front + self._size - 1)\n        return self._nums[last]\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return array for printing\"\"\"\n        # Only convert list elements within the valid length range\n        res = []\n        for i in range(self._size):\n            res.append(self._nums[self.index(self._front + i)])\n        return res\n
array_deque.cpp
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n  private:\n    vector<int> nums; // Array for storing double-ended queue elements\n    int front;        // Front pointer, points to the front of the queue element\n    int queSize;      // Double-ended queue length\n\n  public:\n    /* Constructor */\n    ArrayDeque(int capacity) {\n        nums.resize(capacity);\n        front = queSize = 0;\n    }\n\n    /* Get the capacity of the double-ended queue */\n    int capacity() {\n        return nums.size();\n    }\n\n    /* Get the length of the double-ended queue */\n    int size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    bool isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate circular array index */\n    int index(int i) {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + capacity()) % capacity();\n    }\n\n    /* Front of the queue enqueue */\n    void pushFirst(int num) {\n        if (queSize == capacity()) {\n            cout << \"Double-ended queue is full\" << endl;\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        front = index(front - 1);\n        // Add num to front of queue\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue enqueue */\n    void pushLast(int num) {\n        if (queSize == capacity()) {\n            cout << \"Double-ended queue is full\" << endl;\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        int rear = index(front + queSize);\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue dequeue */\n    int popFirst() {\n        int num = peekFirst();\n        // Move front pointer backward by one position\n        front = index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Access rear of the queue element */\n    int popLast() {\n        int num = peekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    int peekFirst() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return nums[front];\n    }\n\n    /* Driver Code */\n    int peekLast() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        // Initialize double-ended queue\n        int last = index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return array for printing */\n    vector<int> toVector() {\n        // Elements enqueue\n        vector<int> res(queSize);\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[index(j)];\n        }\n        return res;\n    }\n};\n
array_deque.java
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n    private int[] nums; // Array for storing double-ended queue elements\n    private int front; // Front pointer, points to the front of the queue element\n    private int queSize; // Double-ended queue length\n\n    /* Constructor */\n    public ArrayDeque(int capacity) {\n        this.nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the capacity of the double-ended queue */\n    public int capacity() {\n        return nums.length;\n    }\n\n    /* Get the length of the double-ended queue */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    public boolean isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate circular array index */\n    private int index(int i) {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + capacity()) % capacity();\n    }\n\n    /* Front of the queue enqueue */\n    public void pushFirst(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        front = index(front - 1);\n        // Add num to front of queue\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue enqueue */\n    public void pushLast(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        int rear = index(front + queSize);\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue dequeue */\n    public int popFirst() {\n        int num = peekFirst();\n        // Move front pointer backward by one position\n        front = index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Access rear of the queue element */\n    public int popLast() {\n        int num = peekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int peekFirst() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return nums[front];\n    }\n\n    /* Driver Code */\n    public int peekLast() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        // Initialize double-ended queue\n        int last = index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return array for printing */\n    public int[] toArray() {\n        // Elements enqueue\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[index(j)];\n        }\n        return res;\n    }\n}\n
array_deque.cs
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n    int[] nums;  // Array for storing double-ended queue elements\n    int front;   // Front pointer, points to the front of the queue element\n    int queSize; // Double-ended queue length\n\n    /* Constructor */\n    public ArrayDeque(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the capacity of the double-ended queue */\n    int Capacity() {\n        return nums.Length;\n    }\n\n    /* Get the length of the double-ended queue */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    public bool IsEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate circular array index */\n    int Index(int i) {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + Capacity()) % Capacity();\n    }\n\n    /* Front of the queue enqueue */\n    public void PushFirst(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        front = Index(front - 1);\n        // Add num to front of queue\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue enqueue */\n    public void PushLast(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        int rear = Index(front + queSize);\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue dequeue */\n    public int PopFirst() {\n        int num = PeekFirst();\n        // Move front pointer backward by one position\n        front = Index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Access rear of the queue element */\n    public int PopLast() {\n        int num = PeekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int PeekFirst() {\n        if (IsEmpty()) {\n            throw new InvalidOperationException();\n        }\n        return nums[front];\n    }\n\n    /* Driver Code */\n    public int PeekLast() {\n        if (IsEmpty()) {\n            throw new InvalidOperationException();\n        }\n        // Initialize double-ended queue\n        int last = Index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return array for printing */\n    public int[] ToArray() {\n        // Elements enqueue\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[Index(j)];\n        }\n        return res;\n    }\n}\n
array_deque.go
/* Double-ended queue based on circular array implementation */\ntype arrayDeque struct {\n    nums        []int // Array for storing double-ended queue elements\n    front       int   // Front pointer, points to the front of the queue element\n    queSize     int   // Double-ended queue length\n    queCapacity int   // Queue capacity (maximum number of elements)\n}\n\n/* Access front of the queue element */\nfunc newArrayDeque(queCapacity int) *arrayDeque {\n    return &arrayDeque{\n        nums:        make([]int, queCapacity),\n        queCapacity: queCapacity,\n        front:       0,\n        queSize:     0,\n    }\n}\n\n/* Get the length of the double-ended queue */\nfunc (q *arrayDeque) size() int {\n    return q.queSize\n}\n\n/* Check if the double-ended queue is empty */\nfunc (q *arrayDeque) isEmpty() bool {\n    return q.queSize == 0\n}\n\n/* Calculate circular array index */\nfunc (q *arrayDeque) index(i int) int {\n    // Use modulo operation to wrap the array head and tail together\n    // When i passes the tail of the array, return to the head\n    // When i passes the head of the array, return to the tail\n    return (i + q.queCapacity) % q.queCapacity\n}\n\n/* Front of the queue enqueue */\nfunc (q *arrayDeque) pushFirst(num int) {\n    if q.queSize == q.queCapacity {\n        fmt.Println(\"Double-ended queue is full\")\n        return\n    }\n    // Use modulo operation to wrap front around to the tail after passing the head of the array\n    // Add num to the front of the queue\n    q.front = q.index(q.front - 1)\n    // Add num to front of queue\n    q.nums[q.front] = num\n    q.queSize++\n}\n\n/* Rear of the queue enqueue */\nfunc (q *arrayDeque) pushLast(num int) {\n    if q.queSize == q.queCapacity {\n        fmt.Println(\"Double-ended queue is full\")\n        return\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    rear := q.index(q.front + q.queSize)\n    // Front pointer moves one position backward\n    q.nums[rear] = num\n    q.queSize++\n}\n\n/* Rear of the queue dequeue */\nfunc (q *arrayDeque) popFirst() any {\n    num := q.peekFirst()\n    if num == nil {\n        return nil\n    }\n    // Move front pointer backward by one position\n    q.front = q.index(q.front + 1)\n    q.queSize--\n    return num\n}\n\n/* Access rear of the queue element */\nfunc (q *arrayDeque) popLast() any {\n    num := q.peekLast()\n    if num == nil {\n        return nil\n    }\n    q.queSize--\n    return num\n}\n\n/* Return list for printing */\nfunc (q *arrayDeque) peekFirst() any {\n    if q.isEmpty() {\n        return nil\n    }\n    return q.nums[q.front]\n}\n\n/* Driver Code */\nfunc (q *arrayDeque) peekLast() any {\n    if q.isEmpty() {\n        return nil\n    }\n    // Initialize double-ended queue\n    last := q.index(q.front + q.queSize - 1)\n    return q.nums[last]\n}\n\n/* Get Slice for printing */\nfunc (q *arrayDeque) toSlice() []int {\n    // Elements enqueue\n    res := make([]int, q.queSize)\n    for i, j := 0, q.front; i < q.queSize; i++ {\n        res[i] = q.nums[q.index(j)]\n        j++\n    }\n    return res\n}\n
array_deque.swift
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n    private var nums: [Int] // Array for storing double-ended queue elements\n    private var front: Int // Front pointer, points to the front of the queue element\n    private var _size: Int // Double-ended queue length\n\n    /* Constructor */\n    init(capacity: Int) {\n        nums = Array(repeating: 0, count: capacity)\n        front = 0\n        _size = 0\n    }\n\n    /* Get the capacity of the double-ended queue */\n    func capacity() -> Int {\n        nums.count\n    }\n\n    /* Get the length of the double-ended queue */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check if the double-ended queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Calculate circular array index */\n    private func index(i: Int) -> Int {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        (i + capacity()) % capacity()\n    }\n\n    /* Front of the queue enqueue */\n    func pushFirst(num: Int) {\n        if size() == capacity() {\n            print(\"Double-ended queue is full\")\n            return\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        front = index(i: front - 1)\n        // Add num to front of queue\n        nums[front] = num\n        _size += 1\n    }\n\n    /* Rear of the queue enqueue */\n    func pushLast(num: Int) {\n        if size() == capacity() {\n            print(\"Double-ended queue is full\")\n            return\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        let rear = index(i: front + size())\n        // Front pointer moves one position backward\n        nums[rear] = num\n        _size += 1\n    }\n\n    /* Rear of the queue dequeue */\n    func popFirst() -> Int {\n        let num = peekFirst()\n        // Move front pointer backward by one position\n        front = index(i: front + 1)\n        _size -= 1\n        return num\n    }\n\n    /* Access rear of the queue element */\n    func popLast() -> Int {\n        let num = peekLast()\n        _size -= 1\n        return num\n    }\n\n    /* Return list for printing */\n    func peekFirst() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return nums[front]\n    }\n\n    /* Driver Code */\n    func peekLast() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        // Initialize double-ended queue\n        let last = index(i: front + size() - 1)\n        return nums[last]\n    }\n\n    /* Return array for printing */\n    func toArray() -> [Int] {\n        // Elements enqueue\n        (front ..< front + size()).map { nums[index(i: $0)] }\n    }\n}\n
array_deque.js
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n    #nums; // Array for storing double-ended queue elements\n    #front; // Front pointer, points to the front of the queue element\n    #queSize; // Double-ended queue length\n\n    /* Constructor */\n    constructor(capacity) {\n        this.#nums = new Array(capacity);\n        this.#front = 0;\n        this.#queSize = 0;\n    }\n\n    /* Get the capacity of the double-ended queue */\n    capacity() {\n        return this.#nums.length;\n    }\n\n    /* Get the length of the double-ended queue */\n    size() {\n        return this.#queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Calculate circular array index */\n    index(i) {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + this.capacity()) % this.capacity();\n    }\n\n    /* Front of the queue enqueue */\n    pushFirst(num) {\n        if (this.#queSize === this.capacity()) {\n            console.log('Double-ended queue is full');\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        this.#front = this.index(this.#front - 1);\n        // Add num to front of queue\n        this.#nums[this.#front] = num;\n        this.#queSize++;\n    }\n\n    /* Rear of the queue enqueue */\n    pushLast(num) {\n        if (this.#queSize === this.capacity()) {\n            console.log('Double-ended queue is full');\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        const rear = this.index(this.#front + this.#queSize);\n        // Front pointer moves one position backward\n        this.#nums[rear] = num;\n        this.#queSize++;\n    }\n\n    /* Rear of the queue dequeue */\n    popFirst() {\n        const num = this.peekFirst();\n        // Move front pointer backward by one position\n        this.#front = this.index(this.#front + 1);\n        this.#queSize--;\n        return num;\n    }\n\n    /* Access rear of the queue element */\n    popLast() {\n        const num = this.peekLast();\n        this.#queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peekFirst() {\n        if (this.isEmpty()) throw new Error('The Deque Is Empty.');\n        return this.#nums[this.#front];\n    }\n\n    /* Driver Code */\n    peekLast() {\n        if (this.isEmpty()) throw new Error('The Deque Is Empty.');\n        // Initialize double-ended queue\n        const last = this.index(this.#front + this.#queSize - 1);\n        return this.#nums[last];\n    }\n\n    /* Return array for printing */\n    toArray() {\n        // Elements enqueue\n        const res = [];\n        for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) {\n            res[i] = this.#nums[this.index(j)];\n        }\n        return res;\n    }\n}\n
array_deque.ts
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n    private nums: number[]; // Array for storing double-ended queue elements\n    private front: number; // Front pointer, points to the front of the queue element\n    private queSize: number; // Double-ended queue length\n\n    /* Constructor */\n    constructor(capacity: number) {\n        this.nums = new Array(capacity);\n        this.front = 0;\n        this.queSize = 0;\n    }\n\n    /* Get the capacity of the double-ended queue */\n    capacity(): number {\n        return this.nums.length;\n    }\n\n    /* Get the length of the double-ended queue */\n    size(): number {\n        return this.queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Calculate circular array index */\n    index(i: number): number {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + this.capacity()) % this.capacity();\n    }\n\n    /* Front of the queue enqueue */\n    pushFirst(num: number): void {\n        if (this.queSize === this.capacity()) {\n            console.log('Double-ended queue is full');\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        this.front = this.index(this.front - 1);\n        // Add num to front of queue\n        this.nums[this.front] = num;\n        this.queSize++;\n    }\n\n    /* Rear of the queue enqueue */\n    pushLast(num: number): void {\n        if (this.queSize === this.capacity()) {\n            console.log('Double-ended queue is full');\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        const rear: number = this.index(this.front + this.queSize);\n        // Front pointer moves one position backward\n        this.nums[rear] = num;\n        this.queSize++;\n    }\n\n    /* Rear of the queue dequeue */\n    popFirst(): number {\n        const num: number = this.peekFirst();\n        // Move front pointer backward by one position\n        this.front = this.index(this.front + 1);\n        this.queSize--;\n        return num;\n    }\n\n    /* Access rear of the queue element */\n    popLast(): number {\n        const num: number = this.peekLast();\n        this.queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peekFirst(): number {\n        if (this.isEmpty()) throw new Error('The Deque Is Empty.');\n        return this.nums[this.front];\n    }\n\n    /* Driver Code */\n    peekLast(): number {\n        if (this.isEmpty()) throw new Error('The Deque Is Empty.');\n        // Initialize double-ended queue\n        const last = this.index(this.front + this.queSize - 1);\n        return this.nums[last];\n    }\n\n    /* Return array for printing */\n    toArray(): number[] {\n        // Elements enqueue\n        const res: number[] = [];\n        for (let i = 0, j = this.front; i < this.queSize; i++, j++) {\n            res[i] = this.nums[this.index(j)];\n        }\n        return res;\n    }\n}\n
array_deque.dart
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n  late List<int> _nums; // Array for storing double-ended queue elements\n  late int _front; // Front pointer, points to the front of the queue element\n  late int _queSize; // Double-ended queue length\n\n  /* Constructor */\n  ArrayDeque(int capacity) {\n    this._nums = List.filled(capacity, 0);\n    this._front = this._queSize = 0;\n  }\n\n  /* Get the capacity of the double-ended queue */\n  int capacity() {\n    return _nums.length;\n  }\n\n  /* Get the length of the double-ended queue */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check if the double-ended queue is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Calculate circular array index */\n  int index(int i) {\n    // Use modulo operation to wrap the array head and tail together\n    // When i passes the tail of the array, return to the head\n    // When i passes the head of the array, return to the tail\n    return (i + capacity()) % capacity();\n  }\n\n  /* Front of the queue enqueue */\n  void pushFirst(int _num) {\n    if (_queSize == capacity()) {\n      throw Exception(\"Double-ended queue is full\");\n    }\n    // Use modulo operation to wrap front around to the tail after passing the head of the array\n    // Use modulo operation to wrap _front from array head back to tail\n    _front = index(_front - 1);\n    // Add _num to queue front\n    _nums[_front] = _num;\n    _queSize++;\n  }\n\n  /* Rear of the queue enqueue */\n  void pushLast(int _num) {\n    if (_queSize == capacity()) {\n      throw Exception(\"Double-ended queue is full\");\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    int rear = index(_front + _queSize);\n    // Add _num to queue rear\n    _nums[rear] = _num;\n    _queSize++;\n  }\n\n  /* Rear of the queue dequeue */\n  int popFirst() {\n    int _num = peekFirst();\n    // Move front pointer right by one\n    _front = index(_front + 1);\n    _queSize--;\n    return _num;\n  }\n\n  /* Access rear of the queue element */\n  int popLast() {\n    int _num = peekLast();\n    _queSize--;\n    return _num;\n  }\n\n  /* Return list for printing */\n  int peekFirst() {\n    if (isEmpty()) {\n      throw Exception(\"Deque is empty\");\n    }\n    return _nums[_front];\n  }\n\n  /* Driver Code */\n  int peekLast() {\n    if (isEmpty()) {\n      throw Exception(\"Deque is empty\");\n    }\n    // Initialize double-ended queue\n    int last = index(_front + _queSize - 1);\n    return _nums[last];\n  }\n\n  /* Return array for printing */\n  List<int> toArray() {\n    // Elements enqueue\n    List<int> res = List.filled(_queSize, 0);\n    for (int i = 0, j = _front; i < _queSize; i++, j++) {\n      res[i] = _nums[index(j)];\n    }\n    return res;\n  }\n}\n
array_deque.rs
/* Double-ended queue based on circular array implementation */\nstruct ArrayDeque<T> {\n    nums: Vec<T>,    // Array for storing double-ended queue elements\n    front: usize,    // Front pointer, points to the front of the queue element\n    que_size: usize, // Double-ended queue length\n}\n\nimpl<T: Copy + Default> ArrayDeque<T> {\n    /* Constructor */\n    pub fn new(capacity: usize) -> Self {\n        Self {\n            nums: vec![T::default(); capacity],\n            front: 0,\n            que_size: 0,\n        }\n    }\n\n    /* Get the capacity of the double-ended queue */\n    pub fn capacity(&self) -> usize {\n        self.nums.len()\n    }\n\n    /* Get the length of the double-ended queue */\n    pub fn size(&self) -> usize {\n        self.que_size\n    }\n\n    /* Check if the double-ended queue is empty */\n    pub fn is_empty(&self) -> bool {\n        self.que_size == 0\n    }\n\n    /* Calculate circular array index */\n    fn index(&self, i: i32) -> usize {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        ((i + self.capacity() as i32) % self.capacity() as i32) as usize\n    }\n\n    /* Front of the queue enqueue */\n    pub fn push_first(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        self.front = self.index(self.front as i32 - 1);\n        // Add num to front of queue\n        self.nums[self.front] = num;\n        self.que_size += 1;\n    }\n\n    /* Rear of the queue enqueue */\n    pub fn push_last(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        let rear = self.index(self.front as i32 + self.que_size as i32);\n        // Front pointer moves one position backward\n        self.nums[rear] = num;\n        self.que_size += 1;\n    }\n\n    /* Rear of the queue dequeue */\n    fn pop_first(&mut self) -> T {\n        let num = self.peek_first();\n        // Move front pointer backward by one position\n        self.front = self.index(self.front as i32 + 1);\n        self.que_size -= 1;\n        num\n    }\n\n    /* Access rear of the queue element */\n    fn pop_last(&mut self) -> T {\n        let num = self.peek_last();\n        self.que_size -= 1;\n        num\n    }\n\n    /* Return list for printing */\n    fn peek_first(&self) -> T {\n        if self.is_empty() {\n            panic!(\"Deque is empty\")\n        };\n        self.nums[self.front]\n    }\n\n    /* Driver Code */\n    fn peek_last(&self) -> T {\n        if self.is_empty() {\n            panic!(\"Deque is empty\")\n        };\n        // Initialize double-ended queue\n        let last = self.index(self.front as i32 + self.que_size as i32 - 1);\n        self.nums[last]\n    }\n\n    /* Return array for printing */\n    fn to_array(&self) -> Vec<T> {\n        // Elements enqueue\n        let mut res = vec![T::default(); self.que_size];\n        let mut j = self.front;\n        for i in 0..self.que_size {\n            res[i] = self.nums[self.index(j as i32)];\n            j += 1;\n        }\n        res\n    }\n}\n
array_deque.c
/* Double-ended queue based on circular array implementation */\ntypedef struct {\n    int *nums;       // Array for storing queue elements\n    int front;       // Front pointer, points to the front of the queue element\n    int queSize;     // Rear pointer, points to rear + 1\n    int queCapacity; // Queue capacity\n} ArrayDeque;\n\n/* Constructor */\nArrayDeque *newArrayDeque(int capacity) {\n    ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque));\n    // Initialize array\n    deque->queCapacity = capacity;\n    deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity);\n    deque->front = deque->queSize = 0;\n    return deque;\n}\n\n/* Destructor */\nvoid delArrayDeque(ArrayDeque *deque) {\n    free(deque->nums);\n    free(deque);\n}\n\n/* Get the capacity of the double-ended queue */\nint capacity(ArrayDeque *deque) {\n    return deque->queCapacity;\n}\n\n/* Get the length of the double-ended queue */\nint size(ArrayDeque *deque) {\n    return deque->queSize;\n}\n\n/* Check if the double-ended queue is empty */\nbool empty(ArrayDeque *deque) {\n    return deque->queSize == 0;\n}\n\n/* Calculate circular array index */\nint dequeIndex(ArrayDeque *deque, int i) {\n    // Use modulo operation to wrap the array head and tail together\n    // When i exceeds array end, wrap to head\n    // When i passes the head of the array, return to the tail\n    return ((i + capacity(deque)) % capacity(deque));\n}\n\n/* Front of the queue enqueue */\nvoid pushFirst(ArrayDeque *deque, int num) {\n    if (deque->queSize == capacity(deque)) {\n        printf(\"Deque is full\\r\\n\");\n        return;\n    }\n    // Use modulo operation to wrap front around to the tail after passing the head of the array\n    // Use modulo to wrap front from array head to rear\n    deque->front = dequeIndex(deque, deque->front - 1);\n    // Add num to queue front\n    deque->nums[deque->front] = num;\n    deque->queSize++;\n}\n\n/* Rear of the queue enqueue */\nvoid pushLast(ArrayDeque *deque, int num) {\n    if (deque->queSize == capacity(deque)) {\n        printf(\"Deque is full\\r\\n\");\n        return;\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    int rear = dequeIndex(deque, deque->front + deque->queSize);\n    // Front pointer moves one position backward\n    deque->nums[rear] = num;\n    deque->queSize++;\n}\n\n/* Return list for printing */\nint peekFirst(ArrayDeque *deque) {\n    // Access error: Deque is empty\n    assert(empty(deque) == 0);\n    return deque->nums[deque->front];\n}\n\n/* Driver Code */\nint peekLast(ArrayDeque *deque) {\n    // Access error: Deque is empty\n    assert(empty(deque) == 0);\n    int last = dequeIndex(deque, deque->front + deque->queSize - 1);\n    return deque->nums[last];\n}\n\n/* Rear of the queue dequeue */\nint popFirst(ArrayDeque *deque) {\n    int num = peekFirst(deque);\n    // Move front pointer backward by one position\n    deque->front = dequeIndex(deque, deque->front + 1);\n    deque->queSize--;\n    return num;\n}\n\n/* Access rear of the queue element */\nint popLast(ArrayDeque *deque) {\n    int num = peekLast(deque);\n    deque->queSize--;\n    return num;\n}\n\n/* Return array for printing */\nint *toArray(ArrayDeque *deque, int *queSize) {\n    *queSize = deque->queSize;\n    int *res = (int *)calloc(deque->queSize, sizeof(int));\n    int j = deque->front;\n    for (int i = 0; i < deque->queSize; i++) {\n        res[i] = deque->nums[j % deque->queCapacity];\n        j++;\n    }\n    return res;\n}\n
array_deque.kt
/* Constructor */\nclass ArrayDeque(capacity: Int) {\n    private var nums: IntArray = IntArray(capacity) // Array for storing double-ended queue elements\n    private var front: Int = 0 // Front pointer, points to the front of the queue element\n    private var queSize: Int = 0 // Double-ended queue length\n\n    /* Get the capacity of the double-ended queue */\n    fun capacity(): Int {\n        return nums.size\n    }\n\n    /* Get the length of the double-ended queue */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check if the double-ended queue is empty */\n    fun isEmpty(): Boolean {\n        return queSize == 0\n    }\n\n    /* Calculate circular array index */\n    private fun index(i: Int): Int {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + capacity()) % capacity()\n    }\n\n    /* Front of the queue enqueue */\n    fun pushFirst(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Double-ended queue is full\")\n            return\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        front = index(front - 1)\n        // Add num to front of queue\n        nums[front] = num\n        queSize++\n    }\n\n    /* Rear of the queue enqueue */\n    fun pushLast(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Double-ended queue is full\")\n            return\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        val rear = index(front + queSize)\n        // Front pointer moves one position backward\n        nums[rear] = num\n        queSize++\n    }\n\n    /* Rear of the queue dequeue */\n    fun popFirst(): Int {\n        val num = peekFirst()\n        // Move front pointer backward by one position\n        front = index(front + 1)\n        queSize--\n        return num\n    }\n\n    /* Access rear of the queue element */\n    fun popLast(): Int {\n        val num = peekLast()\n        queSize--\n        return num\n    }\n\n    /* Return list for printing */\n    fun peekFirst(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return nums[front]\n    }\n\n    /* Driver Code */\n    fun peekLast(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        // Initialize double-ended queue\n        val last = index(front + queSize - 1)\n        return nums[last]\n    }\n\n    /* Return array for printing */\n    fun toArray(): IntArray {\n        // Elements enqueue\n        val res = IntArray(queSize)\n        var i = 0\n        var j = front\n        while (i < queSize) {\n            res[i] = nums[index(j)]\n            i++\n            j++\n        }\n        return res\n    }\n}\n
array_deque.rb
### Deque based on circular array ###\nclass ArrayDeque\n  ### Get deque length ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize(capacity)\n    @nums = Array.new(capacity, 0)\n    @front = 0\n    @size = 0\n  end\n\n  ### Get deque capacity ###\n  def capacity\n    @nums.length\n  end\n\n  ### Check if deque is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue at front ###\n  def push_first(num)\n    if size == capacity\n      puts 'Double-ended queue is full'\n      return\n    end\n\n    # Use modulo operation to wrap front around to the tail after passing the head of the array\n    # Add num to the front of the queue\n    @front = index(@front - 1)\n    # Add num to front of queue\n    @nums[@front] = num\n    @size += 1\n  end\n\n  ### Enqueue at rear ###\n  def push_last(num)\n    if size == capacity\n      puts 'Double-ended queue is full'\n      return\n    end\n\n    # Use modulo operation to wrap rear around to the head after passing the tail of the array\n    rear = index(@front + size)\n    # Front pointer moves one position backward\n    @nums[rear] = num\n    @size += 1\n  end\n\n  ### Dequeue from front ###\n  def pop_first\n    num = peek_first\n    # Move front pointer backward by one position\n    @front = index(@front + 1)\n    @size -= 1\n    num\n  end\n\n  ### Dequeue from rear ###\n  def pop_last\n    num = peek_last\n    @size -= 1\n    num\n  end\n\n  ### Access front element ###\n  def peek_first\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @nums[@front]\n  end\n\n  ### Access rear element ###\n  def peek_last\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    # Initialize double-ended queue\n    last = index(@front + size - 1)\n    @nums[last]\n  end\n\n  ### Return array for printing ###\n  def to_array\n    # Elements enqueue\n    res = []\n    for i in 0...size\n      res << @nums[index(@front + i)]\n    end\n    res\n  end\n\n  private\n\n  ### Calculate circular array index ###\n  def index(i)\n    # Use modulo operation to wrap the array head and tail together\n    # When i passes the tail of the array, return to the head\n    # When i passes the head of the array, return to the tail\n    (i + capacity) % capacity\n  end\nend\n
","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#533-deque-applications","level":2,"title":"5.3.3   Deque Applications","text":"

A deque combines the logic of both stacks and queues. Therefore, it can implement all application scenarios of both, while providing greater flexibility.

We know that the \"undo\" function in software is typically implemented using a stack: the system pushes each change operation onto the stack and then implements undo through pop. However, considering system resource limitations, software usually limits the number of undo steps (for example, only allowing 50 steps to be saved). When the stack length exceeds 50, the software needs to perform a deletion operation at the bottom of the stack (front of the queue). But a stack cannot implement this functionality, so a deque is needed to replace the stack. Note that the core logic of \"undo\" still follows the LIFO principle of a stack; it's just that the deque can more flexibly implement some additional logic.

","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/queue/","level":1,"title":"5.2   Queue","text":"

A queue is a linear data structure that follows the First In First Out (FIFO) rule. As the name suggests, a queue simulates the phenomenon of lining up, where newcomers continuously join the end of the queue, while people at the front of the queue leave one by one.

As shown in Figure 5-4, we call the front of the queue the \"front\" and the end the \"rear.\" The operation of adding an element to the rear is called \"enqueue,\" and the operation of removing the front element is called \"dequeue.\"

Figure 5-4   FIFO rule of queue

","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#521-common-queue-operations","level":2,"title":"5.2.1   Common Queue Operations","text":"

The common operations on a queue are shown in Table 5-2. Note that method names may vary across different programming languages. We adopt the same naming convention as for stacks here.

Table 5-2   Efficiency of Queue Operations

Method Description Time Complexity push() Enqueue element, add element to rear \\(O(1)\\) pop() Dequeue front element \\(O(1)\\) peek() Access front element \\(O(1)\\)

We can directly use the ready-made queue classes in programming languages:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby queue.py
from collections import deque\n\n# Initialize queue\n# In Python, we generally use the deque class as a queue\n# Although queue.Queue() is a pure queue class, it is not very user-friendly, so it is not recommended\nque: deque[int] = deque()\n\n# Enqueue elements\nque.append(1)\nque.append(3)\nque.append(2)\nque.append(5)\nque.append(4)\n\n# Access front element\nfront: int = que[0]\n\n# Dequeue element\npop: int = que.popleft()\n\n# Get queue length\nsize: int = len(que)\n\n# Check if queue is empty\nis_empty: bool = len(que) == 0\n
queue.cpp
/* Initialize queue */\nqueue<int> queue;\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access front element */\nint front = queue.front();\n\n/* Dequeue element */\nqueue.pop();\n\n/* Get queue length */\nint size = queue.size();\n\n/* Check if queue is empty */\nbool empty = queue.empty();\n
queue.java
/* Initialize queue */\nQueue<Integer> queue = new LinkedList<>();\n\n/* Enqueue elements */\nqueue.offer(1);\nqueue.offer(3);\nqueue.offer(2);\nqueue.offer(5);\nqueue.offer(4);\n\n/* Access front element */\nint peek = queue.peek();\n\n/* Dequeue element */\nint pop = queue.poll();\n\n/* Get queue length */\nint size = queue.size();\n\n/* Check if queue is empty */\nboolean isEmpty = queue.isEmpty();\n
queue.cs
/* Initialize queue */\nQueue<int> queue = new();\n\n/* Enqueue elements */\nqueue.Enqueue(1);\nqueue.Enqueue(3);\nqueue.Enqueue(2);\nqueue.Enqueue(5);\nqueue.Enqueue(4);\n\n/* Access front element */\nint peek = queue.Peek();\n\n/* Dequeue element */\nint pop = queue.Dequeue();\n\n/* Get queue length */\nint size = queue.Count;\n\n/* Check if queue is empty */\nbool isEmpty = queue.Count == 0;\n
queue_test.go
/* Initialize queue */\n// In Go, use list as a queue\nqueue := list.New()\n\n/* Enqueue elements */\nqueue.PushBack(1)\nqueue.PushBack(3)\nqueue.PushBack(2)\nqueue.PushBack(5)\nqueue.PushBack(4)\n\n/* Access front element */\npeek := queue.Front()\n\n/* Dequeue element */\npop := queue.Front()\nqueue.Remove(pop)\n\n/* Get queue length */\nsize := queue.Len()\n\n/* Check if queue is empty */\nisEmpty := queue.Len() == 0\n
queue.swift
/* Initialize queue */\n// Swift does not have a built-in queue class, can use Array as a queue\nvar queue: [Int] = []\n\n/* Enqueue elements */\nqueue.append(1)\nqueue.append(3)\nqueue.append(2)\nqueue.append(5)\nqueue.append(4)\n\n/* Access front element */\nlet peek = queue.first!\n\n/* Dequeue element */\n// Since it's an array, removeFirst has O(n) complexity\nlet pool = queue.removeFirst()\n\n/* Get queue length */\nlet size = queue.count\n\n/* Check if queue is empty */\nlet isEmpty = queue.isEmpty\n
queue.js
/* Initialize queue */\n// JavaScript does not have a built-in queue, can use Array as a queue\nconst queue = [];\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access front element */\nconst peek = queue[0];\n\n/* Dequeue element */\n// The underlying structure is an array, so shift() has O(n) time complexity\nconst pop = queue.shift();\n\n/* Get queue length */\nconst size = queue.length;\n\n/* Check if queue is empty */\nconst empty = queue.length === 0;\n
queue.ts
/* Initialize queue */\n// TypeScript does not have a built-in queue, can use Array as a queue\nconst queue: number[] = [];\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access front element */\nconst peek = queue[0];\n\n/* Dequeue element */\n// The underlying structure is an array, so shift() has O(n) time complexity\nconst pop = queue.shift();\n\n/* Get queue length */\nconst size = queue.length;\n\n/* Check if queue is empty */\nconst empty = queue.length === 0;\n
queue.dart
/* Initialize queue */\n// In Dart, the Queue class is a deque and can also be used as a queue\nQueue<int> queue = Queue();\n\n/* Enqueue elements */\nqueue.add(1);\nqueue.add(3);\nqueue.add(2);\nqueue.add(5);\nqueue.add(4);\n\n/* Access front element */\nint peek = queue.first;\n\n/* Dequeue element */\nint pop = queue.removeFirst();\n\n/* Get queue length */\nint size = queue.length;\n\n/* Check if queue is empty */\nbool isEmpty = queue.isEmpty;\n
queue.rs
/* Initialize deque */\n// In Rust, use deque as a regular queue\nlet mut deque: VecDeque<u32> = VecDeque::new();\n\n/* Enqueue elements */\ndeque.push_back(1);\ndeque.push_back(3);\ndeque.push_back(2);\ndeque.push_back(5);\ndeque.push_back(4);\n\n/* Access front element */\nif let Some(front) = deque.front() {\n}\n\n/* Dequeue element */\nif let Some(pop) = deque.pop_front() {\n}\n\n/* Get queue length */\nlet size = deque.len();\n\n/* Check if queue is empty */\nlet is_empty = deque.is_empty();\n
queue.c
// C does not provide a built-in queue\n
queue.kt
/* Initialize queue */\nval queue = LinkedList<Int>()\n\n/* Enqueue elements */\nqueue.offer(1)\nqueue.offer(3)\nqueue.offer(2)\nqueue.offer(5)\nqueue.offer(4)\n\n/* Access front element */\nval peek = queue.peek()\n\n/* Dequeue element */\nval pop = queue.poll()\n\n/* Get queue length */\nval size = queue.size\n\n/* Check if queue is empty */\nval isEmpty = queue.isEmpty()\n
queue.rb
# Initialize queue\n# Ruby's built-in queue (Thread::Queue) does not have peek and traversal methods, can use Array as a queue\nqueue = []\n\n# Enqueue elements\nqueue.push(1)\nqueue.push(3)\nqueue.push(2)\nqueue.push(5)\nqueue.push(4)\n\n# Access front element\npeek = queue.first\n\n# Dequeue element\n# Please note that since it's an array, Array#shift has O(n) time complexity\npop = queue.shift\n\n# Get queue length\nsize = queue.length\n\n# Check if queue is empty\nis_empty = queue.empty?\n
Code Visualization

Full Screen >

","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#522-queue-implementation","level":2,"title":"5.2.2   Queue Implementation","text":"

To implement a queue, we need a data structure that allows adding elements at one end and removing elements at the other end. Both linked lists and arrays meet this requirement.

","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#1-linked-list-implementation","level":3,"title":"1.   Linked List Implementation","text":"

As shown in Figure 5-5, we can treat the \"head node\" and \"tail node\" of a linked list as the \"front\" and \"rear\" of the queue, respectively, with the rule that nodes can only be added at the rear and removed from the front.

LinkedListQueuepush()pop()

Figure 5-5   Enqueue and dequeue operations in linked list implementation of queue

Below is the code for implementing a queue using a linked list:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_queue.py
class LinkedListQueue:\n    \"\"\"Queue based on linked list implementation\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._front: ListNode | None = None  # Head node front\n        self._rear: ListNode | None = None  # Tail node rear\n        self._size: int = 0\n\n    def size(self) -> int:\n        \"\"\"Get the length of the queue\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the queue is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int):\n        \"\"\"Enqueue\"\"\"\n        # Add num after the tail node\n        node = ListNode(num)\n        # If the queue is empty, make both front and rear point to the node\n        if self._front is None:\n            self._front = node\n            self._rear = node\n        # If the queue is not empty, add the node after the tail node\n        else:\n            self._rear.next = node\n            self._rear = node\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Dequeue\"\"\"\n        num = self.peek()\n        # Delete head node\n        self._front = self._front.next\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access front of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Queue is empty\")\n        return self._front.val\n\n    def to_list(self) -> list[int]:\n        \"\"\"Convert to list for printing\"\"\"\n        queue = []\n        temp = self._front\n        while temp:\n            queue.append(temp.val)\n            temp = temp.next\n        return queue\n
linkedlist_queue.cpp
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n  private:\n    ListNode *front, *rear; // Head node front, tail node rear\n    int queSize;\n\n  public:\n    LinkedListQueue() {\n        front = nullptr;\n        rear = nullptr;\n        queSize = 0;\n    }\n\n    ~LinkedListQueue() {\n        // Traverse linked list to delete nodes and free memory\n        freeMemoryLinkedList(front);\n    }\n\n    /* Get the length of the queue */\n    int size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    bool isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    void push(int num) {\n        // Add num after the tail node\n        ListNode *node = new ListNode(num);\n        // If the queue is empty, make both front and rear point to the node\n        if (front == nullptr) {\n            front = node;\n            rear = node;\n        }\n        // If the queue is not empty, add the node after the tail node\n        else {\n            rear->next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    int pop() {\n        int num = peek();\n        // Delete head node\n        ListNode *tmp = front;\n        front = front->next;\n        // Free memory\n        delete tmp;\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    int peek() {\n        if (size() == 0)\n            throw out_of_range(\"Queue is empty\");\n        return front->val;\n    }\n\n    /* Convert linked list to Vector and return */\n    vector<int> toVector() {\n        ListNode *node = front;\n        vector<int> res(size());\n        for (int i = 0; i < res.size(); i++) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
linkedlist_queue.java
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n    private ListNode front, rear; // Head node front, tail node rear\n    private int queSize = 0;\n\n    public LinkedListQueue() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the length of the queue */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue */\n    public void push(int num) {\n        // Add num after the tail node\n        ListNode node = new ListNode(num);\n        // If the queue is empty, make both front and rear point to the node\n        if (front == null) {\n            front = node;\n            rear = node;\n        // If the queue is not empty, add the node after the tail node\n        } else {\n            rear.next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int pop() {\n        int num = peek();\n        // Delete head node\n        front = front.next;\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return front.val;\n    }\n\n    /* Convert linked list to Array and return */\n    public int[] toArray() {\n        ListNode node = front;\n        int[] res = new int[size()];\n        for (int i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_queue.cs
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n    ListNode? front, rear;  // Head node front, tail node rear\n    int queSize = 0;\n\n    public LinkedListQueue() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the length of the queue */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Enqueue */\n    public void Push(int num) {\n        // Add num after the tail node\n        ListNode node = new(num);\n        // If the queue is empty, make both front and rear point to the node\n        if (front == null) {\n            front = node;\n            rear = node;\n            // If the queue is not empty, add the node after the tail node\n        } else if (rear != null) {\n            rear.next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int Pop() {\n        int num = Peek();\n        // Delete head node\n        front = front?.next;\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return front!.val;\n    }\n\n    /* Convert linked list to Array and return */\n    public int[] ToArray() {\n        if (front == null)\n            return [];\n\n        ListNode? node = front;\n        int[] res = new int[Size()];\n        for (int i = 0; i < res.Length; i++) {\n            res[i] = node!.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_queue.go
/* Queue based on linked list implementation */\ntype linkedListQueue struct {\n    // Use built-in package list to implement queue\n    data *list.List\n}\n\n/* Access front of the queue element */\nfunc newLinkedListQueue() *linkedListQueue {\n    return &linkedListQueue{\n        data: list.New(),\n    }\n}\n\n/* Enqueue */\nfunc (s *linkedListQueue) push(value any) {\n    s.data.PushBack(value)\n}\n\n/* Dequeue */\nfunc (s *linkedListQueue) pop() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Return list for printing */\nfunc (s *linkedListQueue) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    return e.Value\n}\n\n/* Get the length of the queue */\nfunc (s *linkedListQueue) size() int {\n    return s.data.Len()\n}\n\n/* Check if the queue is empty */\nfunc (s *linkedListQueue) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get List for printing */\nfunc (s *linkedListQueue) toList() *list.List {\n    return s.data\n}\n
linkedlist_queue.swift
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n    private var front: ListNode? // Head node\n    private var rear: ListNode? // Tail node\n    private var _size: Int\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the length of the queue */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check if the queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue */\n    func push(num: Int) {\n        // Add num after the tail node\n        let node = ListNode(x: num)\n        // If the queue is empty, make both front and rear point to the node\n        if front == nil {\n            front = node\n            rear = node\n        }\n        // If the queue is not empty, add the node after the tail node\n        else {\n            rear?.next = node\n            rear = node\n        }\n        _size += 1\n    }\n\n    /* Dequeue */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        // Delete head node\n        front = front?.next\n        _size -= 1\n        return num\n    }\n\n    /* Return list for printing */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Queue is empty\")\n        }\n        return front!.val\n    }\n\n    /* Convert linked list to Array and return */\n    func toArray() -> [Int] {\n        var node = front\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
linkedlist_queue.js
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n    #front; // Front node #front\n    #rear; // Rear node #rear\n    #queSize = 0;\n\n    constructor() {\n        this.#front = null;\n        this.#rear = null;\n    }\n\n    /* Get the length of the queue */\n    get size() {\n        return this.#queSize;\n    }\n\n    /* Check if the queue is empty */\n    isEmpty() {\n        return this.size === 0;\n    }\n\n    /* Enqueue */\n    push(num) {\n        // Add num after the tail node\n        const node = new ListNode(num);\n        // If the queue is empty, make both front and rear point to the node\n        if (!this.#front) {\n            this.#front = node;\n            this.#rear = node;\n            // If the queue is not empty, add the node after the tail node\n        } else {\n            this.#rear.next = node;\n            this.#rear = node;\n        }\n        this.#queSize++;\n    }\n\n    /* Dequeue */\n    pop() {\n        const num = this.peek();\n        // Delete head node\n        this.#front = this.#front.next;\n        this.#queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek() {\n        if (this.size === 0) throw new Error('Queue is empty');\n        return this.#front.val;\n    }\n\n    /* Convert linked list to Array and return */\n    toArray() {\n        let node = this.#front;\n        const res = new Array(this.size);\n        for (let i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_queue.ts
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n    private front: ListNode | null; // Head node front\n    private rear: ListNode | null; // Tail node rear\n    private queSize: number = 0;\n\n    constructor() {\n        this.front = null;\n        this.rear = null;\n    }\n\n    /* Get the length of the queue */\n    get size(): number {\n        return this.queSize;\n    }\n\n    /* Check if the queue is empty */\n    isEmpty(): boolean {\n        return this.size === 0;\n    }\n\n    /* Enqueue */\n    push(num: number): void {\n        // Add num after the tail node\n        const node = new ListNode(num);\n        // If the queue is empty, make both front and rear point to the node\n        if (!this.front) {\n            this.front = node;\n            this.rear = node;\n            // If the queue is not empty, add the node after the tail node\n        } else {\n            this.rear!.next = node;\n            this.rear = node;\n        }\n        this.queSize++;\n    }\n\n    /* Dequeue */\n    pop(): number {\n        const num = this.peek();\n        if (!this.front) throw new Error('Queue is empty');\n        // Delete head node\n        this.front = this.front.next;\n        this.queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek(): number {\n        if (this.size === 0) throw new Error('Queue is empty');\n        return this.front!.val;\n    }\n\n    /* Convert linked list to Array and return */\n    toArray(): number[] {\n        let node = this.front;\n        const res = new Array<number>(this.size);\n        for (let i = 0; i < res.length; i++) {\n            res[i] = node!.val;\n            node = node!.next;\n        }\n        return res;\n    }\n}\n
linkedlist_queue.dart
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n  ListNode? _front; // Head node _front\n  ListNode? _rear; // Tail node _rear\n  int _queSize = 0; // Queue length\n\n  LinkedListQueue() {\n    _front = null;\n    _rear = null;\n  }\n\n  /* Get the length of the queue */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check if the queue is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Enqueue */\n  void push(int _num) {\n    // Add _num after tail node\n    final node = ListNode(_num);\n    // If the queue is empty, make both front and rear point to the node\n    if (_front == null) {\n      _front = node;\n      _rear = node;\n    } else {\n      // If the queue is not empty, add the node after the tail node\n      _rear!.next = node;\n      _rear = node;\n    }\n    _queSize++;\n  }\n\n  /* Dequeue */\n  int pop() {\n    final int _num = peek();\n    // Delete head node\n    _front = _front!.next;\n    _queSize--;\n    return _num;\n  }\n\n  /* Return list for printing */\n  int peek() {\n    if (_queSize == 0) {\n      throw Exception('Queue is empty');\n    }\n    return _front!.val;\n  }\n\n  /* Convert linked list to Array and return */\n  List<int> toArray() {\n    ListNode? node = _front;\n    final List<int> queue = [];\n    while (node != null) {\n      queue.add(node.val);\n      node = node.next;\n    }\n    return queue;\n  }\n}\n
linkedlist_queue.rs
/* Queue based on linked list implementation */\n#[allow(dead_code)]\npub struct LinkedListQueue<T> {\n    front: Option<Rc<RefCell<ListNode<T>>>>, // Head node front\n    rear: Option<Rc<RefCell<ListNode<T>>>>,  // Tail node rear\n    que_size: usize,                         // Queue length\n}\n\nimpl<T: Copy> LinkedListQueue<T> {\n    pub fn new() -> Self {\n        Self {\n            front: None,\n            rear: None,\n            que_size: 0,\n        }\n    }\n\n    /* Get the length of the queue */\n    pub fn size(&self) -> usize {\n        return self.que_size;\n    }\n\n    /* Check if the queue is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.que_size == 0;\n    }\n\n    /* Enqueue */\n    pub fn push(&mut self, num: T) {\n        // Add num after the tail node\n        let new_rear = ListNode::new(num);\n        match self.rear.take() {\n            // If the queue is not empty, add the node after the tail node\n            Some(old_rear) => {\n                old_rear.borrow_mut().next = Some(new_rear.clone());\n                self.rear = Some(new_rear);\n            }\n            // If the queue is empty, make both front and rear point to the node\n            None => {\n                self.front = Some(new_rear.clone());\n                self.rear = Some(new_rear);\n            }\n        }\n        self.que_size += 1;\n    }\n\n    /* Dequeue */\n    pub fn pop(&mut self) -> Option<T> {\n        self.front.take().map(|old_front| {\n            match old_front.borrow_mut().next.take() {\n                Some(new_front) => {\n                    self.front = Some(new_front);\n                }\n                None => {\n                    self.rear.take();\n                }\n            }\n            self.que_size -= 1;\n            old_front.borrow().val\n        })\n    }\n\n    /* Return list for printing */\n    pub fn peek(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.front.as_ref()\n    }\n\n    /* Convert linked list to Array and return */\n    pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n        let mut res: Vec<T> = Vec::new();\n\n        fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {\n            if let Some(cur) = cur {\n                res.push(cur.borrow().val);\n                recur(cur.borrow().next.as_ref(), res);\n            }\n        }\n\n        recur(head, &mut res);\n\n        res\n    }\n}\n
linkedlist_queue.c
/* Queue based on linked list implementation */\ntypedef struct {\n    ListNode *front, *rear;\n    int queSize;\n} LinkedListQueue;\n\n/* Constructor */\nLinkedListQueue *newLinkedListQueue() {\n    LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue));\n    queue->front = NULL;\n    queue->rear = NULL;\n    queue->queSize = 0;\n    return queue;\n}\n\n/* Destructor */\nvoid delLinkedListQueue(LinkedListQueue *queue) {\n    // Free all nodes\n    while (queue->front != NULL) {\n        ListNode *tmp = queue->front;\n        queue->front = queue->front->next;\n        free(tmp);\n    }\n    // Free queue structure\n    free(queue);\n}\n\n/* Get the length of the queue */\nint size(LinkedListQueue *queue) {\n    return queue->queSize;\n}\n\n/* Check if the queue is empty */\nbool empty(LinkedListQueue *queue) {\n    return (size(queue) == 0);\n}\n\n/* Enqueue */\nvoid push(LinkedListQueue *queue, int num) {\n    // Add node at tail\n    ListNode *node = newListNode(num);\n    // If the queue is empty, make both front and rear point to the node\n    if (queue->front == NULL) {\n        queue->front = node;\n        queue->rear = node;\n    }\n    // If the queue is not empty, add the node after the tail node\n    else {\n        queue->rear->next = node;\n        queue->rear = node;\n    }\n    queue->queSize++;\n}\n\n/* Return list for printing */\nint peek(LinkedListQueue *queue) {\n    assert(size(queue) && queue->front);\n    return queue->front->val;\n}\n\n/* Dequeue */\nint pop(LinkedListQueue *queue) {\n    int num = peek(queue);\n    ListNode *tmp = queue->front;\n    queue->front = queue->front->next;\n    free(tmp);\n    queue->queSize--;\n    return num;\n}\n\n/* Print queue */\nvoid printLinkedListQueue(LinkedListQueue *queue) {\n    int *arr = malloc(sizeof(int) * queue->queSize);\n    // Copy data from list to array\n    int i;\n    ListNode *node;\n    for (i = 0, node = queue->front; i < queue->queSize; i++) {\n        arr[i] = node->val;\n        node = node->next;\n    }\n    printArray(arr, queue->queSize);\n    free(arr);\n}\n
linkedlist_queue.kt
/* Queue based on linked list implementation */\nclass LinkedListQueue(\n    // Head node front, tail node rear\n    private var front: ListNode? = null,\n    private var rear: ListNode? = null,\n    private var queSize: Int = 0\n) {\n\n    /* Get the length of the queue */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check if the queue is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Enqueue */\n    fun push(num: Int) {\n        // Add num after the tail node\n        val node = ListNode(num)\n        // If the queue is empty, make both front and rear point to the node\n        if (front == null) {\n            front = node\n            rear = node\n            // If the queue is not empty, add the node after the tail node\n        } else {\n            rear?.next = node\n            rear = node\n        }\n        queSize++\n    }\n\n    /* Dequeue */\n    fun pop(): Int {\n        val num = peek()\n        // Delete head node\n        front = front?.next\n        queSize--\n        return num\n    }\n\n    /* Return list for printing */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return front!!._val\n    }\n\n    /* Convert linked list to Array and return */\n    fun toArray(): IntArray {\n        var node = front\n        val res = IntArray(size())\n        for (i in res.indices) {\n            res[i] = node!!._val\n            node = node.next\n        }\n        return res\n    }\n}\n
linkedlist_queue.rb
### Queue based on linked list ###\nclass LinkedListQueue\n  ### Get queue length ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @front = nil  # Head node front\n    @rear = nil   # Tail node rear\n    @size = 0\n  end\n\n  ### Check if queue is empty ###\n  def is_empty?\n    @front.nil?\n  end\n\n  ### Enqueue ###\n  def push(num)\n    # Add num after the tail node\n    node = ListNode.new(num)\n\n    # If queue is empty, set both front and rear to this node\n    if @front.nil?\n      @front = node\n      @rear = node\n    # If queue is not empty, add this node after rear\n    else\n      @rear.next = node\n      @rear = node\n    end\n\n    @size += 1\n  end\n\n  ### Dequeue ###\n  def pop\n    num = peek\n    # Delete head node\n    @front = @front.next\n    @size -= 1\n    num\n  end\n\n  ### Access front element ###\n  def peek\n    raise IndexError, 'Queue is empty' if is_empty?\n\n    @front.val\n  end\n\n  ### Convert linked list to Array and return ###\n  def to_array\n    queue = []\n    temp = @front\n    while temp\n      queue << temp.val\n      temp = temp.next\n    end\n    queue\n  end\nend\n
","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#2-array-implementation","level":3,"title":"2.   Array Implementation","text":"

Deleting the first element in an array has a time complexity of \\(O(n)\\), which would make the dequeue operation inefficient. However, we can use the following clever method to avoid this problem.

We can use a variable front to point to the index of the front element and maintain a variable size to record the queue length. We define rear = front + size, which calculates the position right after the rear element.

Based on this design, the valid interval containing elements in the array is [front, rear - 1]. The implementation methods for various operations are shown in Figure 5-6:

  • Enqueue operation: Assign the input element to the rear index and increase size by 1.
  • Dequeue operation: Simply increase front by 1 and decrease size by 1.

As you can see, both enqueue and dequeue operations require only one operation, with a time complexity of \\(O(1)\\).

ArrayQueuepush()pop()

Figure 5-6   Enqueue and dequeue operations in array implementation of queue

You may notice a problem: as we continuously enqueue and dequeue, both front and rear move to the right. When they reach the end of the array, they cannot continue moving. To solve this problem, we can treat the array as a \"circular array\" with head and tail connected.

For a circular array, we need to let front or rear wrap around to the beginning of the array when they cross the end. This periodic pattern can be implemented using the \"modulo operation,\" as shown in the code below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_queue.py
class ArrayQueue:\n    \"\"\"Queue based on circular array implementation\"\"\"\n\n    def __init__(self, size: int):\n        \"\"\"Constructor\"\"\"\n        self._nums: list[int] = [0] * size  # Array for storing queue elements\n        self._front: int = 0  # Front pointer, points to the front of the queue element\n        self._size: int = 0  # Queue length\n\n    def capacity(self) -> int:\n        \"\"\"Get the capacity of the queue\"\"\"\n        return len(self._nums)\n\n    def size(self) -> int:\n        \"\"\"Get the length of the queue\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the queue is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int):\n        \"\"\"Enqueue\"\"\"\n        if self._size == self.capacity():\n            raise IndexError(\"Queue is full\")\n        # Calculate rear pointer, points to rear index + 1\n        # Use modulo operation to wrap rear around to the head after passing the tail of the array\n        rear: int = (self._front + self._size) % self.capacity()\n        # Add num to the rear of the queue\n        self._nums[rear] = num\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Dequeue\"\"\"\n        num: int = self.peek()\n        # Front pointer moves one position backward, if it passes the tail, return to the head of the array\n        self._front = (self._front + 1) % self.capacity()\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access front of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Queue is empty\")\n        return self._nums[self._front]\n\n    def to_list(self) -> list[int]:\n        \"\"\"Return list for printing\"\"\"\n        res = [0] * self.size()\n        j: int = self._front\n        for i in range(self.size()):\n            res[i] = self._nums[(j % self.capacity())]\n            j += 1\n        return res\n
array_queue.cpp
/* Queue based on circular array implementation */\nclass ArrayQueue {\n  private:\n    int *nums;       // Array for storing queue elements\n    int front;       // Front pointer, points to the front of the queue element\n    int queSize;     // Queue length\n    int queCapacity; // Queue capacity\n\n  public:\n    ArrayQueue(int capacity) {\n        // Initialize array\n        nums = new int[capacity];\n        queCapacity = capacity;\n        front = queSize = 0;\n    }\n\n    ~ArrayQueue() {\n        delete[] nums;\n    }\n\n    /* Get the capacity of the queue */\n    int capacity() {\n        return queCapacity;\n    }\n\n    /* Get the length of the queue */\n    int size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue */\n    void push(int num) {\n        if (queSize == queCapacity) {\n            cout << \"Queue is full\" << endl;\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        int rear = (front + queSize) % queCapacity;\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    int pop() {\n        int num = peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        front = (front + 1) % queCapacity;\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    int peek() {\n        if (isEmpty())\n            throw out_of_range(\"Queue is empty\");\n        return nums[front];\n    }\n\n    /* Convert array to Vector and return */\n    vector<int> toVector() {\n        // Elements enqueue\n        vector<int> arr(queSize);\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            arr[i] = nums[j % queCapacity];\n        }\n        return arr;\n    }\n};\n
array_queue.java
/* Queue based on circular array implementation */\nclass ArrayQueue {\n    private int[] nums; // Array for storing queue elements\n    private int front; // Front pointer, points to the front of the queue element\n    private int queSize; // Queue length\n\n    public ArrayQueue(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the capacity of the queue */\n    public int capacity() {\n        return nums.length;\n    }\n\n    /* Get the length of the queue */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    public boolean isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    public void push(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        int rear = (front + queSize) % capacity();\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int pop() {\n        int num = peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        front = (front + 1) % capacity();\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return nums[front];\n    }\n\n    /* Return array */\n    public int[] toArray() {\n        // Elements enqueue\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[j % capacity()];\n        }\n        return res;\n    }\n}\n
array_queue.cs
/* Queue based on circular array implementation */\nclass ArrayQueue {\n    int[] nums;  // Array for storing queue elements\n    int front;   // Front pointer, points to the front of the queue element\n    int queSize; // Queue length\n\n    public ArrayQueue(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the capacity of the queue */\n    int Capacity() {\n        return nums.Length;\n    }\n\n    /* Get the length of the queue */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    public bool IsEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    public void Push(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        int rear = (front + queSize) % Capacity();\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int Pop() {\n        int num = Peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        front = (front + 1) % Capacity();\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return nums[front];\n    }\n\n    /* Return array */\n    public int[] ToArray() {\n        // Elements enqueue\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[j % this.Capacity()];\n        }\n        return res;\n    }\n}\n
array_queue.go
/* Queue based on circular array implementation */\ntype arrayQueue struct {\n    nums        []int // Array for storing queue elements\n    front       int   // Front pointer, points to the front of the queue element\n    queSize     int   // Queue length\n    queCapacity int   // Queue capacity (maximum number of elements)\n}\n\n/* Access front of the queue element */\nfunc newArrayQueue(queCapacity int) *arrayQueue {\n    return &arrayQueue{\n        nums:        make([]int, queCapacity),\n        queCapacity: queCapacity,\n        front:       0,\n        queSize:     0,\n    }\n}\n\n/* Get the length of the queue */\nfunc (q *arrayQueue) size() int {\n    return q.queSize\n}\n\n/* Check if the queue is empty */\nfunc (q *arrayQueue) isEmpty() bool {\n    return q.queSize == 0\n}\n\n/* Enqueue */\nfunc (q *arrayQueue) push(num int) {\n    // When rear == queCapacity, queue is full\n    if q.queSize == q.queCapacity {\n        return\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    // Add num to the rear of the queue\n    rear := (q.front + q.queSize) % q.queCapacity\n    // Front pointer moves one position backward\n    q.nums[rear] = num\n    q.queSize++\n}\n\n/* Dequeue */\nfunc (q *arrayQueue) pop() any {\n    num := q.peek()\n    if num == nil {\n        return nil\n    }\n\n    // Move front pointer backward by one position, if it passes the tail, return to array head\n    q.front = (q.front + 1) % q.queCapacity\n    q.queSize--\n    return num\n}\n\n/* Return list for printing */\nfunc (q *arrayQueue) peek() any {\n    if q.isEmpty() {\n        return nil\n    }\n    return q.nums[q.front]\n}\n\n/* Get Slice for printing */\nfunc (q *arrayQueue) toSlice() []int {\n    rear := (q.front + q.queSize)\n    if rear >= q.queCapacity {\n        rear %= q.queCapacity\n        return append(q.nums[q.front:], q.nums[:rear]...)\n    }\n    return q.nums[q.front:rear]\n}\n
array_queue.swift
/* Queue based on circular array implementation */\nclass ArrayQueue {\n    private var nums: [Int] // Array for storing queue elements\n    private var front: Int // Front pointer, points to the front of the queue element\n    private var _size: Int // Queue length\n\n    init(capacity: Int) {\n        // Initialize array\n        nums = Array(repeating: 0, count: capacity)\n        front = 0\n        _size = 0\n    }\n\n    /* Get the capacity of the queue */\n    func capacity() -> Int {\n        nums.count\n    }\n\n    /* Get the length of the queue */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check if the queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue */\n    func push(num: Int) {\n        if size() == capacity() {\n            print(\"Queue is full\")\n            return\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        let rear = (front + size()) % capacity()\n        // Front pointer moves one position backward\n        nums[rear] = num\n        _size += 1\n    }\n\n    /* Dequeue */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        front = (front + 1) % capacity()\n        _size -= 1\n        return num\n    }\n\n    /* Return list for printing */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Queue is empty\")\n        }\n        return nums[front]\n    }\n\n    /* Return array */\n    func toArray() -> [Int] {\n        // Elements enqueue\n        (front ..< front + size()).map { nums[$0 % capacity()] }\n    }\n}\n
array_queue.js
/* Queue based on circular array implementation */\nclass ArrayQueue {\n    #nums; // Array for storing queue elements\n    #front = 0; // Front pointer, points to the front of the queue element\n    #queSize = 0; // Queue length\n\n    constructor(capacity) {\n        this.#nums = new Array(capacity);\n    }\n\n    /* Get the capacity of the queue */\n    get capacity() {\n        return this.#nums.length;\n    }\n\n    /* Get the length of the queue */\n    get size() {\n        return this.#queSize;\n    }\n\n    /* Check if the queue is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Enqueue */\n    push(num) {\n        if (this.size === this.capacity) {\n            console.log('Queue is full');\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        const rear = (this.#front + this.size) % this.capacity;\n        // Front pointer moves one position backward\n        this.#nums[rear] = num;\n        this.#queSize++;\n    }\n\n    /* Dequeue */\n    pop() {\n        const num = this.peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        this.#front = (this.#front + 1) % this.capacity;\n        this.#queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek() {\n        if (this.isEmpty()) throw new Error('Queue is empty');\n        return this.#nums[this.#front];\n    }\n\n    /* Return Array */\n    toArray() {\n        // Elements enqueue\n        const arr = new Array(this.size);\n        for (let i = 0, j = this.#front; i < this.size; i++, j++) {\n            arr[i] = this.#nums[j % this.capacity];\n        }\n        return arr;\n    }\n}\n
array_queue.ts
/* Queue based on circular array implementation */\nclass ArrayQueue {\n    private nums: number[]; // Array for storing queue elements\n    private front: number; // Front pointer, points to the front of the queue element\n    private queSize: number; // Queue length\n\n    constructor(capacity: number) {\n        this.nums = new Array(capacity);\n        this.front = this.queSize = 0;\n    }\n\n    /* Get the capacity of the queue */\n    get capacity(): number {\n        return this.nums.length;\n    }\n\n    /* Get the length of the queue */\n    get size(): number {\n        return this.queSize;\n    }\n\n    /* Check if the queue is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Enqueue */\n    push(num: number): void {\n        if (this.size === this.capacity) {\n            console.log('Queue is full');\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        const rear = (this.front + this.queSize) % this.capacity;\n        // Front pointer moves one position backward\n        this.nums[rear] = num;\n        this.queSize++;\n    }\n\n    /* Dequeue */\n    pop(): number {\n        const num = this.peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        this.front = (this.front + 1) % this.capacity;\n        this.queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek(): number {\n        if (this.isEmpty()) throw new Error('Queue is empty');\n        return this.nums[this.front];\n    }\n\n    /* Return Array */\n    toArray(): number[] {\n        // Elements enqueue\n        const arr = new Array(this.size);\n        for (let i = 0, j = this.front; i < this.size; i++, j++) {\n            arr[i] = this.nums[j % this.capacity];\n        }\n        return arr;\n    }\n}\n
array_queue.dart
/* Queue based on circular array implementation */\nclass ArrayQueue {\n  late List<int> _nums; // Array for storing queue elements\n  late int _front; // Front pointer, points to the front of the queue element\n  late int _queSize; // Queue length\n\n  ArrayQueue(int capacity) {\n    _nums = List.filled(capacity, 0);\n    _front = _queSize = 0;\n  }\n\n  /* Get the capacity of the queue */\n  int capaCity() {\n    return _nums.length;\n  }\n\n  /* Get the length of the queue */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check if the queue is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Enqueue */\n  void push(int _num) {\n    if (_queSize == capaCity()) {\n      throw Exception(\"Queue is full\");\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    // Add num to the rear of the queue\n    int rear = (_front + _queSize) % capaCity();\n    // Add _num to queue rear\n    _nums[rear] = _num;\n    _queSize++;\n  }\n\n  /* Dequeue */\n  int pop() {\n    int _num = peek();\n    // Move front pointer backward by one position, if it passes the tail, return to array head\n    _front = (_front + 1) % capaCity();\n    _queSize--;\n    return _num;\n  }\n\n  /* Return list for printing */\n  int peek() {\n    if (isEmpty()) {\n      throw Exception(\"Queue is empty\");\n    }\n    return _nums[_front];\n  }\n\n  /* Return Array */\n  List<int> toArray() {\n    // Elements enqueue\n    final List<int> res = List.filled(_queSize, 0);\n    for (int i = 0, j = _front; i < _queSize; i++, j++) {\n      res[i] = _nums[j % capaCity()];\n    }\n    return res;\n  }\n}\n
array_queue.rs
/* Queue based on circular array implementation */\nstruct ArrayQueue<T> {\n    nums: Vec<T>,      // Array for storing queue elements\n    front: i32,        // Front pointer, points to the front of the queue element\n    que_size: i32,     // Queue length\n    que_capacity: i32, // Queue capacity\n}\n\nimpl<T: Copy + Default> ArrayQueue<T> {\n    /* Constructor */\n    fn new(capacity: i32) -> ArrayQueue<T> {\n        ArrayQueue {\n            nums: vec![T::default(); capacity as usize],\n            front: 0,\n            que_size: 0,\n            que_capacity: capacity,\n        }\n    }\n\n    /* Get the capacity of the queue */\n    fn capacity(&self) -> i32 {\n        self.que_capacity\n    }\n\n    /* Get the length of the queue */\n    fn size(&self) -> i32 {\n        self.que_size\n    }\n\n    /* Check if the queue is empty */\n    fn is_empty(&self) -> bool {\n        self.que_size == 0\n    }\n\n    /* Enqueue */\n    fn push(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        let rear = (self.front + self.que_size) % self.que_capacity;\n        // Front pointer moves one position backward\n        self.nums[rear as usize] = num;\n        self.que_size += 1;\n    }\n\n    /* Dequeue */\n    fn pop(&mut self) -> T {\n        let num = self.peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        self.front = (self.front + 1) % self.que_capacity;\n        self.que_size -= 1;\n        num\n    }\n\n    /* Return list for printing */\n    fn peek(&self) -> T {\n        if self.is_empty() {\n            panic!(\"index out of bounds\");\n        }\n        self.nums[self.front as usize]\n    }\n\n    /* Return array */\n    fn to_vector(&self) -> Vec<T> {\n        let cap = self.que_capacity;\n        let mut j = self.front;\n        let mut arr = vec![T::default(); cap as usize];\n        for i in 0..self.que_size {\n            arr[i as usize] = self.nums[(j % cap) as usize];\n            j += 1;\n        }\n        arr\n    }\n}\n
array_queue.c
/* Queue based on circular array implementation */\ntypedef struct {\n    int *nums;       // Array for storing queue elements\n    int front;       // Front pointer, points to the front of the queue element\n    int queSize;     // Rear pointer, points to rear + 1\n    int queCapacity; // Queue capacity\n} ArrayQueue;\n\n/* Constructor */\nArrayQueue *newArrayQueue(int capacity) {\n    ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue));\n    // Initialize array\n    queue->queCapacity = capacity;\n    queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity);\n    queue->front = queue->queSize = 0;\n    return queue;\n}\n\n/* Destructor */\nvoid delArrayQueue(ArrayQueue *queue) {\n    free(queue->nums);\n    free(queue);\n}\n\n/* Get the capacity of the queue */\nint capacity(ArrayQueue *queue) {\n    return queue->queCapacity;\n}\n\n/* Get the length of the queue */\nint size(ArrayQueue *queue) {\n    return queue->queSize;\n}\n\n/* Check if the queue is empty */\nbool empty(ArrayQueue *queue) {\n    return queue->queSize == 0;\n}\n\n/* Return list for printing */\nint peek(ArrayQueue *queue) {\n    assert(size(queue) != 0);\n    return queue->nums[queue->front];\n}\n\n/* Enqueue */\nvoid push(ArrayQueue *queue, int num) {\n    if (size(queue) == capacity(queue)) {\n        printf(\"Queue is full\\r\\n\");\n        return;\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    // Add num to the rear of the queue\n    int rear = (queue->front + queue->queSize) % queue->queCapacity;\n    // Front pointer moves one position backward\n    queue->nums[rear] = num;\n    queue->queSize++;\n}\n\n/* Dequeue */\nint pop(ArrayQueue *queue) {\n    int num = peek(queue);\n    // Move front pointer backward by one position, if it passes the tail, return to array head\n    queue->front = (queue->front + 1) % queue->queCapacity;\n    queue->queSize--;\n    return num;\n}\n\n/* Return array for printing */\nint *toArray(ArrayQueue *queue, int *queSize) {\n    *queSize = queue->queSize;\n    int *res = (int *)calloc(queue->queSize, sizeof(int));\n    int j = queue->front;\n    for (int i = 0; i < queue->queSize; i++) {\n        res[i] = queue->nums[j % queue->queCapacity];\n        j++;\n    }\n    return res;\n}\n
array_queue.kt
/* Queue based on circular array implementation */\nclass ArrayQueue(capacity: Int) {\n    private val nums: IntArray = IntArray(capacity) // Array for storing queue elements\n    private var front: Int = 0 // Front pointer, points to the front of the queue element\n    private var queSize: Int = 0 // Queue length\n\n    /* Get the capacity of the queue */\n    fun capacity(): Int {\n        return nums.size\n    }\n\n    /* Get the length of the queue */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check if the queue is empty */\n    fun isEmpty(): Boolean {\n        return queSize == 0\n    }\n\n    /* Enqueue */\n    fun push(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Queue is full\")\n            return\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        val rear = (front + queSize) % capacity()\n        // Front pointer moves one position backward\n        nums[rear] = num\n        queSize++\n    }\n\n    /* Dequeue */\n    fun pop(): Int {\n        val num = peek()\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        front = (front + 1) % capacity()\n        queSize--\n        return num\n    }\n\n    /* Return list for printing */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return nums[front]\n    }\n\n    /* Return array */\n    fun toArray(): IntArray {\n        // Elements enqueue\n        val res = IntArray(queSize)\n        var i = 0\n        var j = front\n        while (i < queSize) {\n            res[i] = nums[j % capacity()]\n            i++\n            j++\n        }\n        return res\n    }\n}\n
array_queue.rb
### Queue based on circular array ###\nclass ArrayQueue\n  ### Get queue length ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize(size)\n    @nums = Array.new(size, 0) # Array for storing queue elements\n    @front = 0 # Front pointer, points to the front of the queue element\n    @size = 0 # Queue length\n  end\n\n  ### Get queue capacity ###\n  def capacity\n    @nums.length\n  end\n\n  ### Check if queue is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue ###\n  def push(num)\n    raise IndexError, 'Queue is full' if size == capacity\n\n    # Use modulo operation to wrap rear around to the head after passing the tail of the array\n    # Add num to the rear of the queue\n    rear = (@front + size) % capacity\n    # Front pointer moves one position backward\n    @nums[rear] = num\n    @size += 1\n  end\n\n  ### Dequeue ###\n  def pop\n    num = peek\n    # Move front pointer backward by one position, if it passes the tail, return to array head\n    @front = (@front + 1) % capacity\n    @size -= 1\n    num\n  end\n\n  ### Access front element ###\n  def peek\n    raise IndexError, 'Queue is empty' if is_empty?\n\n    @nums[@front]\n  end\n\n  ### Return list for printing ###\n  def to_array\n    res = Array.new(size, 0)\n    j = @front\n\n    for i in 0...size\n      res[i] = @nums[j % capacity]\n      j += 1\n    end\n\n    res\n  end\nend\n

The queue implemented above still has limitations: its length is immutable. However, this problem is not difficult to solve. We can replace the array with a dynamic array to introduce an expansion mechanism. Interested readers can try to implement this themselves.

The comparison conclusions for the two implementations are consistent with those for stacks and will not be repeated here.

","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#523-typical-applications-of-queue","level":2,"title":"5.2.3   Typical Applications of Queue","text":"
  • Taobao orders. After shoppers place orders, the orders are added to a queue, and the system subsequently processes the orders in the queue according to their sequence. During Double Eleven, massive orders are generated in a short time, and high concurrency becomes a key challenge that engineers need to tackle.
  • Various to-do tasks. Any scenario that needs to implement \"first come, first served\" functionality, such as a printer's task queue or a restaurant's order queue, can effectively maintain the processing order using queues.
","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/stack/","level":1,"title":"5.1   Stack","text":"

A stack is a linear data structure that follows the Last In First Out (LIFO) logic.

We can compare a stack to a pile of plates on a table. If we specify that only one plate can be moved at a time, then to get the bottom plate, we must first remove the plates above it one by one. If we replace the plates with various types of elements (such as integers, characters, objects, etc.), we get the stack data structure.

As shown in Figure 5-1, we call the top of the stacked elements the \"top\" and the bottom the \"base.\" The operation of adding an element to the top is called \"push,\" and the operation of removing the top element is called \"pop.\"

Figure 5-1   LIFO rule of stack

","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#511-common-stack-operations","level":2,"title":"5.1.1   Common Stack Operations","text":"

The common operations on a stack are shown in Table 5-1. The specific method names depend on the programming language used. Here, we use the common naming convention of push(), pop(), and peek().

Table 5-1   Efficiency of Stack Operations

Method Description Time Complexity push() Push element onto stack (add to top) \\(O(1)\\) pop() Pop top element from stack \\(O(1)\\) peek() Access top element \\(O(1)\\)

Typically, we can directly use the built-in stack class provided by the programming language. However, some languages may not provide a dedicated stack class. In these cases, we can use the language's \"array\" or \"linked list\" as a stack and ignore operations unrelated to the stack in the program logic.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby stack.py
# Initialize stack\n# Python does not have a built-in stack class, can use list as a stack\nstack: list[int] = []\n\n# Push elements\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n\n# Access top element\npeek: int = stack[-1]\n\n# Pop element\npop: int = stack.pop()\n\n# Get stack length\nsize: int = len(stack)\n\n# Check if empty\nis_empty: bool = len(stack) == 0\n
stack.cpp
/* Initialize stack */\nstack<int> stack;\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access top element */\nint top = stack.top();\n\n/* Pop element */\nstack.pop(); // No return value\n\n/* Get stack length */\nint size = stack.size();\n\n/* Check if empty */\nbool empty = stack.empty();\n
stack.java
/* Initialize stack */\nStack<Integer> stack = new Stack<>();\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access top element */\nint peek = stack.peek();\n\n/* Pop element */\nint pop = stack.pop();\n\n/* Get stack length */\nint size = stack.size();\n\n/* Check if empty */\nboolean isEmpty = stack.isEmpty();\n
stack.cs
/* Initialize stack */\nStack<int> stack = new();\n\n/* Push elements */\nstack.Push(1);\nstack.Push(3);\nstack.Push(2);\nstack.Push(5);\nstack.Push(4);\n\n/* Access top element */\nint peek = stack.Peek();\n\n/* Pop element */\nint pop = stack.Pop();\n\n/* Get stack length */\nint size = stack.Count;\n\n/* Check if empty */\nbool isEmpty = stack.Count == 0;\n
stack_test.go
/* Initialize stack */\n// In Go, it is recommended to use Slice as a stack\nvar stack []int\n\n/* Push elements */\nstack = append(stack, 1)\nstack = append(stack, 3)\nstack = append(stack, 2)\nstack = append(stack, 5)\nstack = append(stack, 4)\n\n/* Access top element */\npeek := stack[len(stack)-1]\n\n/* Pop element */\npop := stack[len(stack)-1]\nstack = stack[:len(stack)-1]\n\n/* Get stack length */\nsize := len(stack)\n\n/* Check if empty */\nisEmpty := len(stack) == 0\n
stack.swift
/* Initialize stack */\n// Swift does not have a built-in stack class, can use Array as a stack\nvar stack: [Int] = []\n\n/* Push elements */\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n\n/* Access top element */\nlet peek = stack.last!\n\n/* Pop element */\nlet pop = stack.removeLast()\n\n/* Get stack length */\nlet size = stack.count\n\n/* Check if empty */\nlet isEmpty = stack.isEmpty\n
stack.js
/* Initialize stack */\n// JavaScript does not have a built-in stack class, can use Array as a stack\nconst stack = [];\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access top element */\nconst peek = stack[stack.length-1];\n\n/* Pop element */\nconst pop = stack.pop();\n\n/* Get stack length */\nconst size = stack.length;\n\n/* Check if empty */\nconst is_empty = stack.length === 0;\n
stack.ts
/* Initialize stack */\n// TypeScript does not have a built-in stack class, can use Array as a stack\nconst stack: number[] = [];\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access top element */\nconst peek = stack[stack.length - 1];\n\n/* Pop element */\nconst pop = stack.pop();\n\n/* Get stack length */\nconst size = stack.length;\n\n/* Check if empty */\nconst is_empty = stack.length === 0;\n
stack.dart
/* Initialize stack */\n// Dart does not have a built-in stack class, can use List as a stack\nList<int> stack = [];\n\n/* Push elements */\nstack.add(1);\nstack.add(3);\nstack.add(2);\nstack.add(5);\nstack.add(4);\n\n/* Access top element */\nint peek = stack.last;\n\n/* Pop element */\nint pop = stack.removeLast();\n\n/* Get stack length */\nint size = stack.length;\n\n/* Check if empty */\nbool isEmpty = stack.isEmpty;\n
stack.rs
/* Initialize stack */\n// Use Vec as a stack\nlet mut stack: Vec<i32> = Vec::new();\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access top element */\nlet top = stack.last().unwrap();\n\n/* Pop element */\nlet pop = stack.pop().unwrap();\n\n/* Get stack length */\nlet size = stack.len();\n\n/* Check if empty */\nlet is_empty = stack.is_empty();\n
stack.c
// C does not provide a built-in stack\n
stack.kt
/* Initialize stack */\nval stack = Stack<Int>()\n\n/* Push elements */\nstack.push(1)\nstack.push(3)\nstack.push(2)\nstack.push(5)\nstack.push(4)\n\n/* Access top element */\nval peek = stack.peek()\n\n/* Pop element */\nval pop = stack.pop()\n\n/* Get stack length */\nval size = stack.size\n\n/* Check if empty */\nval isEmpty = stack.isEmpty()\n
stack.rb
# Initialize stack\n# Ruby does not have a built-in stack class, can use Array as a stack\nstack = []\n\n# Push elements\nstack << 1\nstack << 3\nstack << 2\nstack << 5\nstack << 4\n\n# Access top element\npeek = stack.last\n\n# Pop element\npop = stack.pop\n\n# Get stack length\nsize = stack.length\n\n# Check if empty\nis_empty = stack.empty?\n
Code Visualization

Full Screen >

","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#512-stack-implementation","level":2,"title":"5.1.2   Stack Implementation","text":"

To gain a deeper understanding of how a stack operates, let's try implementing a stack class ourselves.

A stack follows the LIFO principle, so we can only add or remove elements at the top. However, both arrays and linked lists allow adding and removing elements at any position. Therefore, a stack can be viewed as a restricted array or linked list. In other words, we can \"shield\" some irrelevant operations of arrays or linked lists so that their external logic conforms to the characteristics of a stack.

","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#1-linked-list-implementation","level":3,"title":"1.   Linked List Implementation","text":"

When implementing a stack using a linked list, we can treat the head node of the linked list as the top of the stack and the tail node as the base.

As shown in Figure 5-2, for the push operation, we simply insert an element at the head of the linked list. This node insertion method is called the \"head insertion method.\" For the pop operation, we just need to remove the head node from the linked list.

LinkedListStackpush()pop()

Figure 5-2   Push and pop operations in linked list implementation of stack

Below is sample code for implementing a stack based on a linked list:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_stack.py
class LinkedListStack:\n    \"\"\"Stack based on linked list implementation\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._peek: ListNode | None = None\n        self._size: int = 0\n\n    def size(self) -> int:\n        \"\"\"Get the length of the stack\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the stack is empty\"\"\"\n        return self._size == 0\n\n    def push(self, val: int):\n        \"\"\"Push\"\"\"\n        node = ListNode(val)\n        node.next = self._peek\n        self._peek = node\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Pop\"\"\"\n        num = self.peek()\n        self._peek = self._peek.next\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access top of the stack element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._peek.val\n\n    def to_list(self) -> list[int]:\n        \"\"\"Convert to list for printing\"\"\"\n        arr = []\n        node = self._peek\n        while node:\n            arr.append(node.val)\n            node = node.next\n        arr.reverse()\n        return arr\n
linkedlist_stack.cpp
/* Stack based on linked list implementation */\nclass LinkedListStack {\n  private:\n    ListNode *stackTop; // Use head node as stack top\n    int stkSize;        // Stack length\n\n  public:\n    LinkedListStack() {\n        stackTop = nullptr;\n        stkSize = 0;\n    }\n\n    ~LinkedListStack() {\n        // Traverse linked list to delete nodes and free memory\n        freeMemoryLinkedList(stackTop);\n    }\n\n    /* Get the length of the stack */\n    int size() {\n        return stkSize;\n    }\n\n    /* Check if the stack is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    void push(int num) {\n        ListNode *node = new ListNode(num);\n        node->next = stackTop;\n        stackTop = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    int pop() {\n        int num = top();\n        ListNode *tmp = stackTop;\n        stackTop = stackTop->next;\n        // Free memory\n        delete tmp;\n        stkSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    int top() {\n        if (isEmpty())\n            throw out_of_range(\"Stack is empty\");\n        return stackTop->val;\n    }\n\n    /* Convert List to Array and return */\n    vector<int> toVector() {\n        ListNode *node = stackTop;\n        vector<int> res(size());\n        for (int i = res.size() - 1; i >= 0; i--) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
linkedlist_stack.java
/* Stack based on linked list implementation */\nclass LinkedListStack {\n    private ListNode stackPeek; // Use head node as stack top\n    private int stkSize = 0; // Stack length\n\n    public LinkedListStack() {\n        stackPeek = null;\n    }\n\n    /* Get the length of the stack */\n    public int size() {\n        return stkSize;\n    }\n\n    /* Check if the stack is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    public void push(int num) {\n        ListNode node = new ListNode(num);\n        node.next = stackPeek;\n        stackPeek = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    public int pop() {\n        int num = peek();\n        stackPeek = stackPeek.next;\n        stkSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stackPeek.val;\n    }\n\n    /* Convert List to Array and return */\n    public int[] toArray() {\n        ListNode node = stackPeek;\n        int[] res = new int[size()];\n        for (int i = res.length - 1; i >= 0; i--) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_stack.cs
/* Stack based on linked list implementation */\nclass LinkedListStack {\n    ListNode? stackPeek;  // Use head node as stack top\n    int stkSize = 0;   // Stack length\n\n    public LinkedListStack() {\n        stackPeek = null;\n    }\n\n    /* Get the length of the stack */\n    public int Size() {\n        return stkSize;\n    }\n\n    /* Check if the stack is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Push */\n    public void Push(int num) {\n        ListNode node = new(num) {\n            next = stackPeek\n        };\n        stackPeek = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    public int Pop() {\n        int num = Peek();\n        stackPeek = stackPeek!.next;\n        stkSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return stackPeek!.val;\n    }\n\n    /* Convert List to Array and return */\n    public int[] ToArray() {\n        if (stackPeek == null)\n            return [];\n\n        ListNode? node = stackPeek;\n        int[] res = new int[Size()];\n        for (int i = res.Length - 1; i >= 0; i--) {\n            res[i] = node!.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_stack.go
/* Stack based on linked list implementation */\ntype linkedListStack struct {\n    // Use built-in package list to implement stack\n    data *list.List\n}\n\n/* Access top of the stack element */\nfunc newLinkedListStack() *linkedListStack {\n    return &linkedListStack{\n        data: list.New(),\n    }\n}\n\n/* Push */\nfunc (s *linkedListStack) push(value int) {\n    s.data.PushBack(value)\n}\n\n/* Pop */\nfunc (s *linkedListStack) pop() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Return list for printing */\nfunc (s *linkedListStack) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    return e.Value\n}\n\n/* Get the length of the stack */\nfunc (s *linkedListStack) size() int {\n    return s.data.Len()\n}\n\n/* Check if the stack is empty */\nfunc (s *linkedListStack) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get List for printing */\nfunc (s *linkedListStack) toList() *list.List {\n    return s.data\n}\n
linkedlist_stack.swift
/* Stack based on linked list implementation */\nclass LinkedListStack {\n    private var _peek: ListNode? // Use head node as stack top\n    private var _size: Int // Stack length\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the length of the stack */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check if the stack is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Push */\n    func push(num: Int) {\n        let node = ListNode(x: num)\n        node.next = _peek\n        _peek = node\n        _size += 1\n    }\n\n    /* Pop */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        _peek = _peek?.next\n        _size -= 1\n        return num\n    }\n\n    /* Return list for printing */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return _peek!.val\n    }\n\n    /* Convert List to Array and return */\n    func toArray() -> [Int] {\n        var node = _peek\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices.reversed() {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
linkedlist_stack.js
/* Stack based on linked list implementation */\nclass LinkedListStack {\n    #stackPeek; // Use head node as stack top\n    #stkSize = 0; // Stack length\n\n    constructor() {\n        this.#stackPeek = null;\n    }\n\n    /* Get the length of the stack */\n    get size() {\n        return this.#stkSize;\n    }\n\n    /* Check if the stack is empty */\n    isEmpty() {\n        return this.size === 0;\n    }\n\n    /* Push */\n    push(num) {\n        const node = new ListNode(num);\n        node.next = this.#stackPeek;\n        this.#stackPeek = node;\n        this.#stkSize++;\n    }\n\n    /* Pop */\n    pop() {\n        const num = this.peek();\n        this.#stackPeek = this.#stackPeek.next;\n        this.#stkSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek() {\n        if (!this.#stackPeek) throw new Error('Stack is empty');\n        return this.#stackPeek.val;\n    }\n\n    /* Convert linked list to Array and return */\n    toArray() {\n        let node = this.#stackPeek;\n        const res = new Array(this.size);\n        for (let i = res.length - 1; i >= 0; i--) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_stack.ts
/* Stack based on linked list implementation */\nclass LinkedListStack {\n    private stackPeek: ListNode | null; // Use head node as stack top\n    private stkSize: number = 0; // Stack length\n\n    constructor() {\n        this.stackPeek = null;\n    }\n\n    /* Get the length of the stack */\n    get size(): number {\n        return this.stkSize;\n    }\n\n    /* Check if the stack is empty */\n    isEmpty(): boolean {\n        return this.size === 0;\n    }\n\n    /* Push */\n    push(num: number): void {\n        const node = new ListNode(num);\n        node.next = this.stackPeek;\n        this.stackPeek = node;\n        this.stkSize++;\n    }\n\n    /* Pop */\n    pop(): number {\n        const num = this.peek();\n        if (!this.stackPeek) throw new Error('Stack is empty');\n        this.stackPeek = this.stackPeek.next;\n        this.stkSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek(): number {\n        if (!this.stackPeek) throw new Error('Stack is empty');\n        return this.stackPeek.val;\n    }\n\n    /* Convert linked list to Array and return */\n    toArray(): number[] {\n        let node = this.stackPeek;\n        const res = new Array<number>(this.size);\n        for (let i = res.length - 1; i >= 0; i--) {\n            res[i] = node!.val;\n            node = node!.next;\n        }\n        return res;\n    }\n}\n
linkedlist_stack.dart
/* Stack implemented based on linked list class */\nclass LinkedListStack {\n  ListNode? _stackPeek; // Use head node as stack top\n  int _stkSize = 0; // Stack length\n\n  LinkedListStack() {\n    _stackPeek = null;\n  }\n\n  /* Get the length of the stack */\n  int size() {\n    return _stkSize;\n  }\n\n  /* Check if the stack is empty */\n  bool isEmpty() {\n    return _stkSize == 0;\n  }\n\n  /* Push */\n  void push(int _num) {\n    final ListNode node = ListNode(_num);\n    node.next = _stackPeek;\n    _stackPeek = node;\n    _stkSize++;\n  }\n\n  /* Pop */\n  int pop() {\n    final int _num = peek();\n    _stackPeek = _stackPeek!.next;\n    _stkSize--;\n    return _num;\n  }\n\n  /* Return list for printing */\n  int peek() {\n    if (_stackPeek == null) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stackPeek!.val;\n  }\n\n  /* Convert linked list to List and return */\n  List<int> toList() {\n    ListNode? node = _stackPeek;\n    List<int> list = [];\n    while (node != null) {\n      list.add(node.val);\n      node = node.next;\n    }\n    list = list.reversed.toList();\n    return list;\n  }\n}\n
linkedlist_stack.rs
/* Stack based on linked list implementation */\n#[allow(dead_code)]\npub struct LinkedListStack<T> {\n    stack_peek: Option<Rc<RefCell<ListNode<T>>>>, // Use head node as stack top\n    stk_size: usize,                              // Stack length\n}\n\nimpl<T: Copy> LinkedListStack<T> {\n    pub fn new() -> Self {\n        Self {\n            stack_peek: None,\n            stk_size: 0,\n        }\n    }\n\n    /* Get the length of the stack */\n    pub fn size(&self) -> usize {\n        return self.stk_size;\n    }\n\n    /* Check if the stack is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.size() == 0;\n    }\n\n    /* Push */\n    pub fn push(&mut self, num: T) {\n        let node = ListNode::new(num);\n        node.borrow_mut().next = self.stack_peek.take();\n        self.stack_peek = Some(node);\n        self.stk_size += 1;\n    }\n\n    /* Pop */\n    pub fn pop(&mut self) -> Option<T> {\n        self.stack_peek.take().map(|old_head| {\n            self.stack_peek = old_head.borrow_mut().next.take();\n            self.stk_size -= 1;\n\n            old_head.borrow().val\n        })\n    }\n\n    /* Return list for printing */\n    pub fn peek(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.stack_peek.as_ref()\n    }\n\n    /* Convert List to Array and return */\n    pub fn to_array(&self) -> Vec<T> {\n        fn _to_array<T: Sized + Copy>(head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n            if let Some(node) = head {\n                let mut nums = _to_array(node.borrow().next.as_ref());\n                nums.push(node.borrow().val);\n                return nums;\n            }\n            return Vec::new();\n        }\n\n        _to_array(self.peek())\n    }\n}\n
linkedlist_stack.c
/* Stack based on linked list implementation */\ntypedef struct {\n    ListNode *top; // Use head node as stack top\n    int size;      // Stack length\n} LinkedListStack;\n\n/* Constructor */\nLinkedListStack *newLinkedListStack() {\n    LinkedListStack *s = malloc(sizeof(LinkedListStack));\n    s->top = NULL;\n    s->size = 0;\n    return s;\n}\n\n/* Destructor */\nvoid delLinkedListStack(LinkedListStack *s) {\n    while (s->top) {\n        ListNode *n = s->top->next;\n        free(s->top);\n        s->top = n;\n    }\n    free(s);\n}\n\n/* Get the length of the stack */\nint size(LinkedListStack *s) {\n    return s->size;\n}\n\n/* Check if the stack is empty */\nbool isEmpty(LinkedListStack *s) {\n    return size(s) == 0;\n}\n\n/* Push */\nvoid push(LinkedListStack *s, int num) {\n    ListNode *node = (ListNode *)malloc(sizeof(ListNode));\n    node->next = s->top; // Update new node's pointer field\n    node->val = num;     // Update new node's data field\n    s->top = node;       // Update stack top\n    s->size++;           // Update stack size\n}\n\n/* Return list for printing */\nint peek(LinkedListStack *s) {\n    if (s->size == 0) {\n        printf(\"Stack is empty\\n\");\n        return INT_MAX;\n    }\n    return s->top->val;\n}\n\n/* Pop */\nint pop(LinkedListStack *s) {\n    int val = peek(s);\n    ListNode *tmp = s->top;\n    s->top = s->top->next;\n    // Free memory\n    free(tmp);\n    s->size--;\n    return val;\n}\n
linkedlist_stack.kt
/* Stack based on linked list implementation */\nclass LinkedListStack(\n    private var stackPeek: ListNode? = null, // Use head node as stack top\n    private var stkSize: Int = 0 // Stack length\n) {\n\n    /* Get the length of the stack */\n    fun size(): Int {\n        return stkSize\n    }\n\n    /* Check if the stack is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Push */\n    fun push(num: Int) {\n        val node = ListNode(num)\n        node.next = stackPeek\n        stackPeek = node\n        stkSize++\n    }\n\n    /* Pop */\n    fun pop(): Int? {\n        val num = peek()\n        stackPeek = stackPeek?.next\n        stkSize--\n        return num\n    }\n\n    /* Return list for printing */\n    fun peek(): Int? {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stackPeek?._val\n    }\n\n    /* Convert List to Array and return */\n    fun toArray(): IntArray {\n        var node = stackPeek\n        val res = IntArray(size())\n        for (i in res.size - 1 downTo 0) {\n            res[i] = node?._val!!\n            node = node.next\n        }\n        return res\n    }\n}\n
linkedlist_stack.rb
### Stack based on linked list ###\nclass LinkedListStack\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @size = 0\n  end\n\n  ### Check if stack is empty ###\n  def is_empty?\n    @peek.nil?\n  end\n\n  ### Push ###\n  def push(val)\n    node = ListNode.new(val)\n    node.next = @peek\n    @peek = node\n    @size += 1\n  end\n\n  ### Pop ###\n  def pop\n    num = peek\n    @peek = @peek.next\n    @size -= 1\n    num\n  end\n\n  ### Access top element ###\n  def peek\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @peek.val\n  end\n\n  ### Convert linked list to Array and return ###\n  def to_array\n    arr = []\n    node = @peek\n    while node\n      arr << node.val\n      node = node.next\n    end\n    arr.reverse\n  end\nend\n
","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#2-array-implementation","level":3,"title":"2.   Array Implementation","text":"

When implementing a stack using an array, we can treat the end of the array as the top of the stack. As shown in Figure 5-3, push and pop operations correspond to adding and removing elements at the end of the array, both with a time complexity of \\(O(1)\\).

ArrayStackpush()pop()

Figure 5-3   Push and pop operations in array implementation of stack

Since elements pushed onto the stack may increase continuously, we can use a dynamic array, which eliminates the need to handle array expansion ourselves. Here is the sample code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_stack.py
class ArrayStack:\n    \"\"\"Stack based on array implementation\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._stack: list[int] = []\n\n    def size(self) -> int:\n        \"\"\"Get the length of the stack\"\"\"\n        return len(self._stack)\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the stack is empty\"\"\"\n        return self.size() == 0\n\n    def push(self, item: int):\n        \"\"\"Push\"\"\"\n        self._stack.append(item)\n\n    def pop(self) -> int:\n        \"\"\"Pop\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._stack.pop()\n\n    def peek(self) -> int:\n        \"\"\"Access top of the stack element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._stack[-1]\n\n    def to_list(self) -> list[int]:\n        \"\"\"Return list for printing\"\"\"\n        return self._stack\n
array_stack.cpp
/* Stack based on array implementation */\nclass ArrayStack {\n  private:\n    vector<int> stack;\n\n  public:\n    /* Get the length of the stack */\n    int size() {\n        return stack.size();\n    }\n\n    /* Check if the stack is empty */\n    bool isEmpty() {\n        return stack.size() == 0;\n    }\n\n    /* Push */\n    void push(int num) {\n        stack.push_back(num);\n    }\n\n    /* Pop */\n    int pop() {\n        int num = top();\n        stack.pop_back();\n        return num;\n    }\n\n    /* Return list for printing */\n    int top() {\n        if (isEmpty())\n            throw out_of_range(\"Stack is empty\");\n        return stack.back();\n    }\n\n    /* Return Vector */\n    vector<int> toVector() {\n        return stack;\n    }\n};\n
array_stack.java
/* Stack based on array implementation */\nclass ArrayStack {\n    private ArrayList<Integer> stack;\n\n    public ArrayStack() {\n        // Initialize list (dynamic array)\n        stack = new ArrayList<>();\n    }\n\n    /* Get the length of the stack */\n    public int size() {\n        return stack.size();\n    }\n\n    /* Check if the stack is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    public void push(int num) {\n        stack.add(num);\n    }\n\n    /* Pop */\n    public int pop() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stack.remove(size() - 1);\n    }\n\n    /* Return list for printing */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stack.get(size() - 1);\n    }\n\n    /* Convert List to Array and return */\n    public Object[] toArray() {\n        return stack.toArray();\n    }\n}\n
array_stack.cs
/* Stack based on array implementation */\nclass ArrayStack {\n    List<int> stack;\n    public ArrayStack() {\n        // Initialize list (dynamic array)\n        stack = [];\n    }\n\n    /* Get the length of the stack */\n    public int Size() {\n        return stack.Count;\n    }\n\n    /* Check if the stack is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Push */\n    public void Push(int num) {\n        stack.Add(num);\n    }\n\n    /* Pop */\n    public int Pop() {\n        if (IsEmpty())\n            throw new Exception();\n        var val = Peek();\n        stack.RemoveAt(Size() - 1);\n        return val;\n    }\n\n    /* Return list for printing */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return stack[Size() - 1];\n    }\n\n    /* Convert List to Array and return */\n    public int[] ToArray() {\n        return [.. stack];\n    }\n}\n
array_stack.go
/* Stack based on array implementation */\ntype arrayStack struct {\n    data []int // Data\n}\n\n/* Access top of the stack element */\nfunc newArrayStack() *arrayStack {\n    return &arrayStack{\n        // Set stack length to 0, capacity to 16\n        data: make([]int, 0, 16),\n    }\n}\n\n/* Stack length */\nfunc (s *arrayStack) size() int {\n    return len(s.data)\n}\n\n/* Is stack empty */\nfunc (s *arrayStack) isEmpty() bool {\n    return s.size() == 0\n}\n\n/* Push */\nfunc (s *arrayStack) push(v int) {\n    // Slice will automatically expand\n    s.data = append(s.data, v)\n}\n\n/* Pop */\nfunc (s *arrayStack) pop() any {\n    val := s.peek()\n    s.data = s.data[:len(s.data)-1]\n    return val\n}\n\n/* Get stack top element */\nfunc (s *arrayStack) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    val := s.data[len(s.data)-1]\n    return val\n}\n\n/* Get Slice for printing */\nfunc (s *arrayStack) toSlice() []int {\n    return s.data\n}\n
array_stack.swift
/* Stack based on array implementation */\nclass ArrayStack {\n    private var stack: [Int]\n\n    init() {\n        // Initialize list (dynamic array)\n        stack = []\n    }\n\n    /* Get the length of the stack */\n    func size() -> Int {\n        stack.count\n    }\n\n    /* Check if the stack is empty */\n    func isEmpty() -> Bool {\n        stack.isEmpty\n    }\n\n    /* Push */\n    func push(num: Int) {\n        stack.append(num)\n    }\n\n    /* Pop */\n    @discardableResult\n    func pop() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return stack.removeLast()\n    }\n\n    /* Return list for printing */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return stack.last!\n    }\n\n    /* Convert List to Array and return */\n    func toArray() -> [Int] {\n        stack\n    }\n}\n
array_stack.js
/* Stack based on array implementation */\nclass ArrayStack {\n    #stack;\n    constructor() {\n        this.#stack = [];\n    }\n\n    /* Get the length of the stack */\n    get size() {\n        return this.#stack.length;\n    }\n\n    /* Check if the stack is empty */\n    isEmpty() {\n        return this.#stack.length === 0;\n    }\n\n    /* Push */\n    push(num) {\n        this.#stack.push(num);\n    }\n\n    /* Pop */\n    pop() {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.#stack.pop();\n    }\n\n    /* Return list for printing */\n    top() {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.#stack[this.#stack.length - 1];\n    }\n\n    /* Return Array */\n    toArray() {\n        return this.#stack;\n    }\n}\n
array_stack.ts
/* Stack based on array implementation */\nclass ArrayStack {\n    private stack: number[];\n    constructor() {\n        this.stack = [];\n    }\n\n    /* Get the length of the stack */\n    get size(): number {\n        return this.stack.length;\n    }\n\n    /* Check if the stack is empty */\n    isEmpty(): boolean {\n        return this.stack.length === 0;\n    }\n\n    /* Push */\n    push(num: number): void {\n        this.stack.push(num);\n    }\n\n    /* Pop */\n    pop(): number | undefined {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.stack.pop();\n    }\n\n    /* Return list for printing */\n    top(): number | undefined {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.stack[this.stack.length - 1];\n    }\n\n    /* Return Array */\n    toArray() {\n        return this.stack;\n    }\n}\n
array_stack.dart
/* Stack based on array implementation */\nclass ArrayStack {\n  late List<int> _stack;\n  ArrayStack() {\n    _stack = [];\n  }\n\n  /* Get the length of the stack */\n  int size() {\n    return _stack.length;\n  }\n\n  /* Check if the stack is empty */\n  bool isEmpty() {\n    return _stack.isEmpty;\n  }\n\n  /* Push */\n  void push(int _num) {\n    _stack.add(_num);\n  }\n\n  /* Pop */\n  int pop() {\n    if (isEmpty()) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stack.removeLast();\n  }\n\n  /* Return list for printing */\n  int peek() {\n    if (isEmpty()) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stack.last;\n  }\n\n  /* Convert stack to Array and return */\n  List<int> toArray() => _stack;\n}\n
array_stack.rs
/* Stack based on array implementation */\nstruct ArrayStack<T> {\n    stack: Vec<T>,\n}\n\nimpl<T> ArrayStack<T> {\n    /* Access top of the stack element */\n    fn new() -> ArrayStack<T> {\n        ArrayStack::<T> {\n            stack: Vec::<T>::new(),\n        }\n    }\n\n    /* Get the length of the stack */\n    fn size(&self) -> usize {\n        self.stack.len()\n    }\n\n    /* Check if the stack is empty */\n    fn is_empty(&self) -> bool {\n        self.size() == 0\n    }\n\n    /* Push */\n    fn push(&mut self, num: T) {\n        self.stack.push(num);\n    }\n\n    /* Pop */\n    fn pop(&mut self) -> Option<T> {\n        self.stack.pop()\n    }\n\n    /* Return list for printing */\n    fn peek(&self) -> Option<&T> {\n        if self.is_empty() {\n            panic!(\"Stack is empty\")\n        };\n        self.stack.last()\n    }\n\n    /* Return &Vec */\n    fn to_array(&self) -> &Vec<T> {\n        &self.stack\n    }\n}\n
array_stack.c
/* Stack based on array implementation */\ntypedef struct {\n    int *data;\n    int size;\n} ArrayStack;\n\n/* Constructor */\nArrayStack *newArrayStack() {\n    ArrayStack *stack = malloc(sizeof(ArrayStack));\n    // Initialize with large capacity to avoid expansion\n    stack->data = malloc(sizeof(int) * MAX_SIZE);\n    stack->size = 0;\n    return stack;\n}\n\n/* Destructor */\nvoid delArrayStack(ArrayStack *stack) {\n    free(stack->data);\n    free(stack);\n}\n\n/* Get the length of the stack */\nint size(ArrayStack *stack) {\n    return stack->size;\n}\n\n/* Check if the stack is empty */\nbool isEmpty(ArrayStack *stack) {\n    return stack->size == 0;\n}\n\n/* Push */\nvoid push(ArrayStack *stack, int num) {\n    if (stack->size == MAX_SIZE) {\n        printf(\"Stack is full\\n\");\n        return;\n    }\n    stack->data[stack->size] = num;\n    stack->size++;\n}\n\n/* Return list for printing */\nint peek(ArrayStack *stack) {\n    if (stack->size == 0) {\n        printf(\"Stack is empty\\n\");\n        return INT_MAX;\n    }\n    return stack->data[stack->size - 1];\n}\n\n/* Pop */\nint pop(ArrayStack *stack) {\n    int val = peek(stack);\n    stack->size--;\n    return val;\n}\n
array_stack.kt
/* Stack based on array implementation */\nclass ArrayStack {\n    // Initialize list (dynamic array)\n    private val stack = mutableListOf<Int>()\n\n    /* Get the length of the stack */\n    fun size(): Int {\n        return stack.size\n    }\n\n    /* Check if the stack is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Push */\n    fun push(num: Int) {\n        stack.add(num)\n    }\n\n    /* Pop */\n    fun pop(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stack.removeAt(size() - 1)\n    }\n\n    /* Return list for printing */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stack[size() - 1]\n    }\n\n    /* Convert List to Array and return */\n    fun toArray(): Array<Any> {\n        return stack.toTypedArray()\n    }\n}\n
array_stack.rb
### Stack based on array ###\nclass ArrayStack\n  ### Constructor ###\n  def initialize\n    @stack = []\n  end\n\n  ### Get stack length ###\n  def size\n    @stack.length\n  end\n\n  ### Check if stack is empty ###\n  def is_empty?\n    @stack.empty?\n  end\n\n  ### Push ###\n  def push(item)\n    @stack << item\n  end\n\n  ### Pop ###\n  def pop\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @stack.pop\n  end\n\n  ### Access top element ###\n  def peek\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @stack.last\n  end\n\n  ### Return list for printing ###\n  def to_array\n    @stack\n  end\nend\n
","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#513-comparison-of-the-two-implementations","level":2,"title":"5.1.3   Comparison of the Two Implementations","text":"

Supported Operations

Both implementations support all operations defined by the stack. The array implementation additionally supports random access, but this goes beyond the stack definition and is generally not used.

Time Efficiency

In the array-based implementation, both push and pop operations occur in pre-allocated contiguous memory, which has good cache locality and is therefore more efficient. However, if pushing exceeds the array capacity, it triggers an expansion mechanism, causing the time complexity of that particular push operation to become \\(O(n)\\).

In the linked list-based implementation, list expansion is very flexible, and there is no issue of reduced efficiency due to array expansion. However, the push operation requires initializing a node object and modifying pointers, so it is relatively less efficient. Nevertheless, if the pushed elements are already node objects, the initialization step can be omitted, thereby improving efficiency.

In summary, when the elements pushed and popped are basic data types such as int or double, we can draw the following conclusions:

  • The array-based stack implementation has reduced efficiency when expansion is triggered, but since expansion is an infrequent operation, the average efficiency is higher.
  • The linked list-based stack implementation can provide more stable efficiency performance.

Space Efficiency

When initializing a list, the system allocates an \"initial capacity\" that may exceed the actual need. Additionally, the expansion mechanism typically expands at a specific ratio (e.g., 2x), and the capacity after expansion may also exceed actual needs. Therefore, the array-based stack implementation may cause some space wastage.

However, since linked list nodes need to store additional pointers, the space occupied by linked list nodes is relatively large.

In summary, we cannot simply determine which implementation is more memory-efficient and need to analyze the specific situation.

","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#514-typical-applications-of-stack","level":2,"title":"5.1.4   Typical Applications of Stack","text":"
  • Back and forward in browsers, undo and redo in software. Every time we open a new webpage, the browser pushes the previous page onto the stack, allowing us to return to the previous page via the back operation. The back operation is essentially performing a pop. To support both back and forward, two stacks are needed to work together.
  • Program memory management. Each time a function is called, the system adds a stack frame to the top of the stack to record the function's context information. During recursion, the downward recursive phase continuously performs push operations, while the upward backtracking phase continuously performs pop operations.
","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/summary/","level":1,"title":"5.4   Summary","text":"","path":["Chapter 5. Stack and Queue","5.4   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • A stack is a data structure that follows the LIFO principle and can be implemented using arrays or linked lists.
  • In terms of time efficiency, the array implementation of a stack has higher average efficiency, but during expansion, the time complexity of a single push operation degrades to \\(O(n)\\). In contrast, the linked list implementation of a stack provides more stable efficiency performance.
  • In terms of space efficiency, the array implementation of a stack may lead to some degree of space wastage. However, it should be noted that the memory space occupied by linked list nodes is larger than that of array elements.
  • A queue is a data structure that follows the FIFO principle and can also be implemented using arrays or linked lists. The conclusions regarding time efficiency and space efficiency comparisons for queues are similar to those for stacks mentioned above.
  • A deque is a queue with greater flexibility that allows adding and removing elements at both ends.
","path":["Chapter 5. Stack and Queue","5.4   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Is the browser's forward and backward functionality implemented with a doubly linked list?

The forward and backward functionality of a browser is essentially a manifestation of a \"stack.\" When a user visits a new page, that page is added to the top of the stack; when the user clicks the back button, that page is popped from the top of the stack. Using a deque can conveniently implement some additional operations, as mentioned in the \"Deque\" section.

Q: After popping from the stack, do we need to free the memory of the popped node?

If the popped node will still be needed later, then memory does not need to be freed. If it won't be used afterward, languages like Java and Python have automatic garbage collection, so manual memory deallocation is not required; in C and C++, manual memory deallocation is necessary.

Q: A deque seems like two stacks joined together. What is its purpose?

A deque is like a combination of a stack and a queue, or two stacks joined together. It exhibits the logic of both stack and queue, so it can implement all applications of stacks and queues, and is more flexible.

Q: How are undo and redo specifically implemented?

Use two stacks: stack A for undo and stack B for redo.

  1. Whenever the user performs an operation, push this operation onto stack A and clear stack B.
  2. When the user performs \"undo,\" pop the most recent operation from stack A and push it onto stack B.
  3. When the user performs \"redo,\" pop the most recent operation from stack B and push it onto stack A.
","path":["Chapter 5. Stack and Queue","5.4   Summary"],"tags":[]},{"location":"chapter_tree/","level":1,"title":"Chapter 7.   Tree","text":"

Abstract

Towering trees are full of vitality, with deep roots and lush leaves, spreading branches and flourishing.

They show us the vivid form of divide and conquer in data.

","path":["Chapter 7. Tree","Chapter 7.   Tree"],"tags":[]},{"location":"chapter_tree/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 7.1   Binary Tree
  • 7.2   Binary Tree Traversal
  • 7.3   Array Representation of Tree
  • 7.4   Binary Search Tree
  • 7.5   AVL Tree *
  • 7.6   Summary
","path":["Chapter 7. Tree","Chapter 7.   Tree"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/","level":1,"title":"7.3   Array Representation of Binary Trees","text":"

Under the linked list representation, the storage unit of a binary tree is a node TreeNode, and nodes are connected by pointers. The previous section introduced the basic operations of binary trees under the linked list representation.

So, can we use an array to represent a binary tree? The answer is yes.

","path":["Chapter 7. Tree","7.3   Array Representation of Binary Trees"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#731-representing-perfect-binary-trees","level":2,"title":"7.3.1   Representing Perfect Binary Trees","text":"

Let's analyze a simple case first. Given a perfect binary tree, we store all nodes in an array according to the order of level-order traversal, where each node corresponds to a unique array index.

Based on the characteristics of level-order traversal, we can derive a \"mapping formula\" between parent node index and child node indices: If a node's index is \\(i\\), then its left child index is \\(2i + 1\\) and its right child index is \\(2i + 2\\). Figure 7-12 shows the mapping relationships between various node indices.

Figure 7-12   Array representation of a perfect binary tree

The mapping formula plays a role similar to the node references (pointers) in linked lists. Given any node in the array, we can access its left (right) child node using the mapping formula.

","path":["Chapter 7. Tree","7.3   Array Representation of Binary Trees"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#732-representing-any-binary-tree","level":2,"title":"7.3.2   Representing Any Binary Tree","text":"

Perfect binary trees are a special case; in the middle levels of a binary tree, there are typically many None values. Since the level-order traversal sequence does not include these None values, we cannot infer the number and distribution of None values based on this sequence alone. This means multiple binary tree structures can correspond to the same level-order traversal sequence.

As shown in Figure 7-13, given a non-perfect binary tree, the above method of array representation fails.

Figure 7-13   Level-order traversal sequence corresponds to multiple binary tree possibilities

To solve this problem, we can consider explicitly writing out all None values in the level-order traversal sequence. As shown in Figure 7-14, after this treatment, the level-order traversal sequence can uniquely represent a binary tree. Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
# Array representation of a binary tree\n# Using None to represent empty slots\ntree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]\n
/* Array representation of a binary tree */\n// Using the maximum integer value INT_MAX to mark empty slots\nvector<int> tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};\n
/* Array representation of a binary tree */\n// Using the Integer wrapper class allows for using null to mark empty slots\nInteger[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };\n
/* Array representation of a binary tree */\n// Using nullable int (int?) allows for using null to mark empty slots\nint?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
/* Array representation of a binary tree */\n// Using an any type slice, allowing for nil to mark empty slots\ntree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15}\n
/* Array representation of a binary tree */\n// Using optional Int (Int?) allows for using nil to mark empty slots\nlet tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]\n
/* Array representation of a binary tree */\n// Using null to represent empty slots\nlet tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
/* Array representation of a binary tree */\n// Using null to represent empty slots\nlet tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
/* Array representation of a binary tree */\n// Using nullable int (int?) allows for using null to mark empty slots\nList<int?> tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
/* Array representation of a binary tree */\n// Using None to mark empty slots\nlet tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)];\n
/* Array representation of a binary tree */\n// Using the maximum int value to mark empty slots, therefore, node values must not be INT_MAX\nint tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};\n
/* Array representation of a binary tree */\n// Using null to represent empty slots\nval tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 )\n
### Array representation of a binary tree ###\n# Using nil to represent empty slots\ntree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]\n

Figure 7-14   Array representation of any type of binary tree

It's worth noting that complete binary trees are very well-suited for array representation. Recalling the definition of a complete binary tree, None only appears at the bottom level and towards the right, meaning all None values must appear at the end of the level-order traversal sequence.

This means that when using an array to represent a complete binary tree, it's possible to omit storing all None values, which is very convenient. Figure 7-15 gives an example.

Figure 7-15   Array representation of a complete binary tree

The following code implements a binary tree based on array representation, including the following operations:

  • Given a certain node, obtain its value, left (right) child node, and parent node.
  • Obtain the preorder, inorder, postorder, and level-order traversal sequences.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_binary_tree.py
class ArrayBinaryTree:\n    \"\"\"Binary tree class represented by array\"\"\"\n\n    def __init__(self, arr: list[int | None]):\n        \"\"\"Constructor\"\"\"\n        self._tree = list(arr)\n\n    def size(self):\n        \"\"\"List capacity\"\"\"\n        return len(self._tree)\n\n    def val(self, i: int) -> int | None:\n        \"\"\"Get value of node at index i\"\"\"\n        # If index is out of bounds, return None, representing empty position\n        if i < 0 or i >= self.size():\n            return None\n        return self._tree[i]\n\n    def left(self, i: int) -> int | None:\n        \"\"\"Get index of left child node of node at index i\"\"\"\n        return 2 * i + 1\n\n    def right(self, i: int) -> int | None:\n        \"\"\"Get index of right child node of node at index i\"\"\"\n        return 2 * i + 2\n\n    def parent(self, i: int) -> int | None:\n        \"\"\"Get index of parent node of node at index i\"\"\"\n        return (i - 1) // 2\n\n    def level_order(self) -> list[int]:\n        \"\"\"Level-order traversal\"\"\"\n        self.res = []\n        # Traverse array directly\n        for i in range(self.size()):\n            if self.val(i) is not None:\n                self.res.append(self.val(i))\n        return self.res\n\n    def dfs(self, i: int, order: str):\n        \"\"\"Depth-first traversal\"\"\"\n        if self.val(i) is None:\n            return\n        # Preorder traversal\n        if order == \"pre\":\n            self.res.append(self.val(i))\n        self.dfs(self.left(i), order)\n        # Inorder traversal\n        if order == \"in\":\n            self.res.append(self.val(i))\n        self.dfs(self.right(i), order)\n        # Postorder traversal\n        if order == \"post\":\n            self.res.append(self.val(i))\n\n    def pre_order(self) -> list[int]:\n        \"\"\"Preorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"pre\")\n        return self.res\n\n    def in_order(self) -> list[int]:\n        \"\"\"Inorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"in\")\n        return self.res\n\n    def post_order(self) -> list[int]:\n        \"\"\"Postorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"post\")\n        return self.res\n
array_binary_tree.cpp
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n  public:\n    /* Constructor */\n    ArrayBinaryTree(vector<int> arr) {\n        tree = arr;\n    }\n\n    /* List capacity */\n    int size() {\n        return tree.size();\n    }\n\n    /* Get value of node at index i */\n    int val(int i) {\n        // Return INT_MAX if index out of bounds, representing empty position\n        if (i < 0 || i >= size())\n            return INT_MAX;\n        return tree[i];\n    }\n\n    /* Get index of left child node of node at index i */\n    int left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get index of right child node of node at index i */\n    int right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get index of parent node of node at index i */\n    int parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    vector<int> levelOrder() {\n        vector<int> res;\n        // Traverse array directly\n        for (int i = 0; i < size(); i++) {\n            if (val(i) != INT_MAX)\n                res.push_back(val(i));\n        }\n        return res;\n    }\n\n    /* Preorder traversal */\n    vector<int> preOrder() {\n        vector<int> res;\n        dfs(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    vector<int> inOrder() {\n        vector<int> res;\n        dfs(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    vector<int> postOrder() {\n        vector<int> res;\n        dfs(0, \"post\", res);\n        return res;\n    }\n\n  private:\n    vector<int> tree;\n\n    /* Depth-first traversal */\n    void dfs(int i, string order, vector<int> &res) {\n        // If empty position, return\n        if (val(i) == INT_MAX)\n            return;\n        // Preorder traversal\n        if (order == \"pre\")\n            res.push_back(val(i));\n        dfs(left(i), order, res);\n        // Inorder traversal\n        if (order == \"in\")\n            res.push_back(val(i));\n        dfs(right(i), order, res);\n        // Postorder traversal\n        if (order == \"post\")\n            res.push_back(val(i));\n    }\n};\n
array_binary_tree.java
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n    private List<Integer> tree;\n\n    /* Constructor */\n    public ArrayBinaryTree(List<Integer> arr) {\n        tree = new ArrayList<>(arr);\n    }\n\n    /* List capacity */\n    public int size() {\n        return tree.size();\n    }\n\n    /* Get value of node at index i */\n    public Integer val(int i) {\n        // If index out of bounds, return null to represent empty position\n        if (i < 0 || i >= size())\n            return null;\n        return tree.get(i);\n    }\n\n    /* Get index of left child node of node at index i */\n    public Integer left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get index of right child node of node at index i */\n    public Integer right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get index of parent node of node at index i */\n    public Integer parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    public List<Integer> levelOrder() {\n        List<Integer> res = new ArrayList<>();\n        // Traverse array directly\n        for (int i = 0; i < size(); i++) {\n            if (val(i) != null)\n                res.add(val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    private void dfs(Integer i, String order, List<Integer> res) {\n        // If empty position, return\n        if (val(i) == null)\n            return;\n        // Preorder traversal\n        if (\"pre\".equals(order))\n            res.add(val(i));\n        dfs(left(i), order, res);\n        // Inorder traversal\n        if (\"in\".equals(order))\n            res.add(val(i));\n        dfs(right(i), order, res);\n        // Postorder traversal\n        if (\"post\".equals(order))\n            res.add(val(i));\n    }\n\n    /* Preorder traversal */\n    public List<Integer> preOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    public List<Integer> inOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    public List<Integer> postOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"post\", res);\n        return res;\n    }\n}\n
array_binary_tree.cs
/* Binary tree class represented by array */\nclass ArrayBinaryTree(List<int?> arr) {\n    List<int?> tree = new(arr);\n\n    /* List capacity */\n    public int Size() {\n        return tree.Count;\n    }\n\n    /* Get value of node at index i */\n    public int? Val(int i) {\n        // If index out of bounds, return null to represent empty position\n        if (i < 0 || i >= Size())\n            return null;\n        return tree[i];\n    }\n\n    /* Get index of left child node of node at index i */\n    public int Left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get index of right child node of node at index i */\n    public int Right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get index of parent node of node at index i */\n    public int Parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    public List<int> LevelOrder() {\n        List<int> res = [];\n        // Traverse array directly\n        for (int i = 0; i < Size(); i++) {\n            if (Val(i).HasValue)\n                res.Add(Val(i)!.Value);\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    void DFS(int i, string order, List<int> res) {\n        // If empty position, return\n        if (!Val(i).HasValue)\n            return;\n        // Preorder traversal\n        if (order == \"pre\")\n            res.Add(Val(i)!.Value);\n        DFS(Left(i), order, res);\n        // Inorder traversal\n        if (order == \"in\")\n            res.Add(Val(i)!.Value);\n        DFS(Right(i), order, res);\n        // Postorder traversal\n        if (order == \"post\")\n            res.Add(Val(i)!.Value);\n    }\n\n    /* Preorder traversal */\n    public List<int> PreOrder() {\n        List<int> res = [];\n        DFS(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    public List<int> InOrder() {\n        List<int> res = [];\n        DFS(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    public List<int> PostOrder() {\n        List<int> res = [];\n        DFS(0, \"post\", res);\n        return res;\n    }\n}\n
array_binary_tree.go
/* Binary tree class represented by array */\ntype arrayBinaryTree struct {\n    tree []any\n}\n\n/* Constructor */\nfunc newArrayBinaryTree(arr []any) *arrayBinaryTree {\n    return &arrayBinaryTree{\n        tree: arr,\n    }\n}\n\n/* List capacity */\nfunc (abt *arrayBinaryTree) size() int {\n    return len(abt.tree)\n}\n\n/* Get value of node at index i */\nfunc (abt *arrayBinaryTree) val(i int) any {\n    // If index out of bounds, return null to represent empty position\n    if i < 0 || i >= abt.size() {\n        return nil\n    }\n    return abt.tree[i]\n}\n\n/* Get index of left child node of node at index i */\nfunc (abt *arrayBinaryTree) left(i int) int {\n    return 2*i + 1\n}\n\n/* Get index of right child node of node at index i */\nfunc (abt *arrayBinaryTree) right(i int) int {\n    return 2*i + 2\n}\n\n/* Get index of parent node of node at index i */\nfunc (abt *arrayBinaryTree) parent(i int) int {\n    return (i - 1) / 2\n}\n\n/* Level-order traversal */\nfunc (abt *arrayBinaryTree) levelOrder() []any {\n    var res []any\n    // Traverse array directly\n    for i := 0; i < abt.size(); i++ {\n        if abt.val(i) != nil {\n            res = append(res, abt.val(i))\n        }\n    }\n    return res\n}\n\n/* Depth-first traversal */\nfunc (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) {\n    // If empty position, return\n    if abt.val(i) == nil {\n        return\n    }\n    // Preorder traversal\n    if order == \"pre\" {\n        *res = append(*res, abt.val(i))\n    }\n    abt.dfs(abt.left(i), order, res)\n    // Inorder traversal\n    if order == \"in\" {\n        *res = append(*res, abt.val(i))\n    }\n    abt.dfs(abt.right(i), order, res)\n    // Postorder traversal\n    if order == \"post\" {\n        *res = append(*res, abt.val(i))\n    }\n}\n\n/* Preorder traversal */\nfunc (abt *arrayBinaryTree) preOrder() []any {\n    var res []any\n    abt.dfs(0, \"pre\", &res)\n    return res\n}\n\n/* Inorder traversal */\nfunc (abt *arrayBinaryTree) inOrder() []any {\n    var res []any\n    abt.dfs(0, \"in\", &res)\n    return res\n}\n\n/* Postorder traversal */\nfunc (abt *arrayBinaryTree) postOrder() []any {\n    var res []any\n    abt.dfs(0, \"post\", &res)\n    return res\n}\n
array_binary_tree.swift
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n    private var tree: [Int?]\n\n    /* Constructor */\n    init(arr: [Int?]) {\n        tree = arr\n    }\n\n    /* List capacity */\n    func size() -> Int {\n        tree.count\n    }\n\n    /* Get value of node at index i */\n    func val(i: Int) -> Int? {\n        // If index out of bounds, return null to represent empty position\n        if i < 0 || i >= size() {\n            return nil\n        }\n        return tree[i]\n    }\n\n    /* Get index of left child node of node at index i */\n    func left(i: Int) -> Int {\n        2 * i + 1\n    }\n\n    /* Get index of right child node of node at index i */\n    func right(i: Int) -> Int {\n        2 * i + 2\n    }\n\n    /* Get index of parent node of node at index i */\n    func parent(i: Int) -> Int {\n        (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    func levelOrder() -> [Int] {\n        var res: [Int] = []\n        // Traverse array directly\n        for i in 0 ..< size() {\n            if let val = val(i: i) {\n                res.append(val)\n            }\n        }\n        return res\n    }\n\n    /* Depth-first traversal */\n    private func dfs(i: Int, order: String, res: inout [Int]) {\n        // If empty position, return\n        guard let val = val(i: i) else {\n            return\n        }\n        // Preorder traversal\n        if order == \"pre\" {\n            res.append(val)\n        }\n        dfs(i: left(i: i), order: order, res: &res)\n        // Inorder traversal\n        if order == \"in\" {\n            res.append(val)\n        }\n        dfs(i: right(i: i), order: order, res: &res)\n        // Postorder traversal\n        if order == \"post\" {\n            res.append(val)\n        }\n    }\n\n    /* Preorder traversal */\n    func preOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"pre\", res: &res)\n        return res\n    }\n\n    /* Inorder traversal */\n    func inOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"in\", res: &res)\n        return res\n    }\n\n    /* Postorder traversal */\n    func postOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"post\", res: &res)\n        return res\n    }\n}\n
array_binary_tree.js
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n    #tree;\n\n    /* Constructor */\n    constructor(arr) {\n        this.#tree = arr;\n    }\n\n    /* List capacity */\n    size() {\n        return this.#tree.length;\n    }\n\n    /* Get value of node at index i */\n    val(i) {\n        // If index out of bounds, return null to represent empty position\n        if (i < 0 || i >= this.size()) return null;\n        return this.#tree[i];\n    }\n\n    /* Get index of left child node of node at index i */\n    left(i) {\n        return 2 * i + 1;\n    }\n\n    /* Get index of right child node of node at index i */\n    right(i) {\n        return 2 * i + 2;\n    }\n\n    /* Get index of parent node of node at index i */\n    parent(i) {\n        return Math.floor((i - 1) / 2); // Floor division\n    }\n\n    /* Level-order traversal */\n    levelOrder() {\n        let res = [];\n        // Traverse array directly\n        for (let i = 0; i < this.size(); i++) {\n            if (this.val(i) !== null) res.push(this.val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    #dfs(i, order, res) {\n        // If empty position, return\n        if (this.val(i) === null) return;\n        // Preorder traversal\n        if (order === 'pre') res.push(this.val(i));\n        this.#dfs(this.left(i), order, res);\n        // Inorder traversal\n        if (order === 'in') res.push(this.val(i));\n        this.#dfs(this.right(i), order, res);\n        // Postorder traversal\n        if (order === 'post') res.push(this.val(i));\n    }\n\n    /* Preorder traversal */\n    preOrder() {\n        const res = [];\n        this.#dfs(0, 'pre', res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    inOrder() {\n        const res = [];\n        this.#dfs(0, 'in', res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    postOrder() {\n        const res = [];\n        this.#dfs(0, 'post', res);\n        return res;\n    }\n}\n
array_binary_tree.ts
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n    #tree: (number | null)[];\n\n    /* Constructor */\n    constructor(arr: (number | null)[]) {\n        this.#tree = arr;\n    }\n\n    /* List capacity */\n    size(): number {\n        return this.#tree.length;\n    }\n\n    /* Get value of node at index i */\n    val(i: number): number | null {\n        // If index out of bounds, return null to represent empty position\n        if (i < 0 || i >= this.size()) return null;\n        return this.#tree[i];\n    }\n\n    /* Get index of left child node of node at index i */\n    left(i: number): number {\n        return 2 * i + 1;\n    }\n\n    /* Get index of right child node of node at index i */\n    right(i: number): number {\n        return 2 * i + 2;\n    }\n\n    /* Get index of parent node of node at index i */\n    parent(i: number): number {\n        return Math.floor((i - 1) / 2); // Floor division\n    }\n\n    /* Level-order traversal */\n    levelOrder(): number[] {\n        let res = [];\n        // Traverse array directly\n        for (let i = 0; i < this.size(); i++) {\n            if (this.val(i) !== null) res.push(this.val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    #dfs(i: number, order: Order, res: (number | null)[]): void {\n        // If empty position, return\n        if (this.val(i) === null) return;\n        // Preorder traversal\n        if (order === 'pre') res.push(this.val(i));\n        this.#dfs(this.left(i), order, res);\n        // Inorder traversal\n        if (order === 'in') res.push(this.val(i));\n        this.#dfs(this.right(i), order, res);\n        // Postorder traversal\n        if (order === 'post') res.push(this.val(i));\n    }\n\n    /* Preorder traversal */\n    preOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'pre', res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    inOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'in', res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    postOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'post', res);\n        return res;\n    }\n}\n
array_binary_tree.dart
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n  late List<int?> _tree;\n\n  /* Constructor */\n  ArrayBinaryTree(this._tree);\n\n  /* List capacity */\n  int size() {\n    return _tree.length;\n  }\n\n  /* Get value of node at index i */\n  int? val(int i) {\n    // If index out of bounds, return null to represent empty position\n    if (i < 0 || i >= size()) {\n      return null;\n    }\n    return _tree[i];\n  }\n\n  /* Get index of left child node of node at index i */\n  int? left(int i) {\n    return 2 * i + 1;\n  }\n\n  /* Get index of right child node of node at index i */\n  int? right(int i) {\n    return 2 * i + 2;\n  }\n\n  /* Get index of parent node of node at index i */\n  int? parent(int i) {\n    return (i - 1) ~/ 2;\n  }\n\n  /* Level-order traversal */\n  List<int> levelOrder() {\n    List<int> res = [];\n    for (int i = 0; i < size(); i++) {\n      if (val(i) != null) {\n        res.add(val(i)!);\n      }\n    }\n    return res;\n  }\n\n  /* Depth-first traversal */\n  void dfs(int i, String order, List<int?> res) {\n    // If empty position, return\n    if (val(i) == null) {\n      return;\n    }\n    // Preorder traversal\n    if (order == 'pre') {\n      res.add(val(i));\n    }\n    dfs(left(i)!, order, res);\n    // Inorder traversal\n    if (order == 'in') {\n      res.add(val(i));\n    }\n    dfs(right(i)!, order, res);\n    // Postorder traversal\n    if (order == 'post') {\n      res.add(val(i));\n    }\n  }\n\n  /* Preorder traversal */\n  List<int?> preOrder() {\n    List<int?> res = [];\n    dfs(0, 'pre', res);\n    return res;\n  }\n\n  /* Inorder traversal */\n  List<int?> inOrder() {\n    List<int?> res = [];\n    dfs(0, 'in', res);\n    return res;\n  }\n\n  /* Postorder traversal */\n  List<int?> postOrder() {\n    List<int?> res = [];\n    dfs(0, 'post', res);\n    return res;\n  }\n}\n
array_binary_tree.rs
/* Binary tree class represented by array */\nstruct ArrayBinaryTree {\n    tree: Vec<Option<i32>>,\n}\n\nimpl ArrayBinaryTree {\n    /* Constructor */\n    fn new(arr: Vec<Option<i32>>) -> Self {\n        Self { tree: arr }\n    }\n\n    /* List capacity */\n    fn size(&self) -> i32 {\n        self.tree.len() as i32\n    }\n\n    /* Get value of node at index i */\n    fn val(&self, i: i32) -> Option<i32> {\n        // If index is out of bounds, return None, representing empty position\n        if i < 0 || i >= self.size() {\n            None\n        } else {\n            self.tree[i as usize]\n        }\n    }\n\n    /* Get index of left child node of node at index i */\n    fn left(&self, i: i32) -> i32 {\n        2 * i + 1\n    }\n\n    /* Get index of right child node of node at index i */\n    fn right(&self, i: i32) -> i32 {\n        2 * i + 2\n    }\n\n    /* Get index of parent node of node at index i */\n    fn parent(&self, i: i32) -> i32 {\n        (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    fn level_order(&self) -> Vec<i32> {\n        self.tree.iter().filter_map(|&x| x).collect()\n    }\n\n    /* Depth-first traversal */\n    fn dfs(&self, i: i32, order: &'static str, res: &mut Vec<i32>) {\n        if self.val(i).is_none() {\n            return;\n        }\n        let val = self.val(i).unwrap();\n        // Preorder traversal\n        if order == \"pre\" {\n            res.push(val);\n        }\n        self.dfs(self.left(i), order, res);\n        // Inorder traversal\n        if order == \"in\" {\n            res.push(val);\n        }\n        self.dfs(self.right(i), order, res);\n        // Postorder traversal\n        if order == \"post\" {\n            res.push(val);\n        }\n    }\n\n    /* Preorder traversal */\n    fn pre_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"pre\", &mut res);\n        res\n    }\n\n    /* Inorder traversal */\n    fn in_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"in\", &mut res);\n        res\n    }\n\n    /* Postorder traversal */\n    fn post_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"post\", &mut res);\n        res\n    }\n}\n
array_binary_tree.c
/* Binary tree structure in array representation */\ntypedef struct {\n    int *tree;\n    int size;\n} ArrayBinaryTree;\n\n/* Constructor */\nArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) {\n    ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree));\n    abt->tree = malloc(sizeof(int) * arrSize);\n    memcpy(abt->tree, arr, sizeof(int) * arrSize);\n    abt->size = arrSize;\n    return abt;\n}\n\n/* Destructor */\nvoid delArrayBinaryTree(ArrayBinaryTree *abt) {\n    free(abt->tree);\n    free(abt);\n}\n\n/* List capacity */\nint size(ArrayBinaryTree *abt) {\n    return abt->size;\n}\n\n/* Get value of node at index i */\nint val(ArrayBinaryTree *abt, int i) {\n    // Return INT_MAX if index out of bounds, representing empty position\n    if (i < 0 || i >= size(abt))\n        return INT_MAX;\n    return abt->tree[i];\n}\n\n/* Level-order traversal */\nint *levelOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    // Traverse array directly\n    for (int i = 0; i < size(abt); i++) {\n        if (val(abt, i) != INT_MAX)\n            res[index++] = val(abt, i);\n    }\n    *returnSize = index;\n    return res;\n}\n\n/* Depth-first traversal */\nvoid dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) {\n    // If empty position, return\n    if (val(abt, i) == INT_MAX)\n        return;\n    // Preorder traversal\n    if (strcmp(order, \"pre\") == 0)\n        res[(*index)++] = val(abt, i);\n    dfs(abt, left(i), order, res, index);\n    // Inorder traversal\n    if (strcmp(order, \"in\") == 0)\n        res[(*index)++] = val(abt, i);\n    dfs(abt, right(i), order, res, index);\n    // Postorder traversal\n    if (strcmp(order, \"post\") == 0)\n        res[(*index)++] = val(abt, i);\n}\n\n/* Preorder traversal */\nint *preOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"pre\", res, &index);\n    *returnSize = index;\n    return res;\n}\n\n/* Inorder traversal */\nint *inOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"in\", res, &index);\n    *returnSize = index;\n    return res;\n}\n\n/* Postorder traversal */\nint *postOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"post\", res, &index);\n    *returnSize = index;\n    return res;\n}\n
array_binary_tree.kt
/* Binary tree class represented by array */\nclass ArrayBinaryTree(val tree: MutableList<Int?>) {\n    /* List capacity */\n    fun size(): Int {\n        return tree.size\n    }\n\n    /* Get value of node at index i */\n    fun _val(i: Int): Int? {\n        // If index out of bounds, return null to represent empty position\n        if (i < 0 || i >= size()) return null\n        return tree[i]\n    }\n\n    /* Get index of left child node of node at index i */\n    fun left(i: Int): Int {\n        return 2 * i + 1\n    }\n\n    /* Get index of right child node of node at index i */\n    fun right(i: Int): Int {\n        return 2 * i + 2\n    }\n\n    /* Get index of parent node of node at index i */\n    fun parent(i: Int): Int {\n        return (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    fun levelOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        // Traverse array directly\n        for (i in 0..<size()) {\n            if (_val(i) != null)\n                res.add(_val(i))\n        }\n        return res\n    }\n\n    /* Depth-first traversal */\n    fun dfs(i: Int, order: String, res: MutableList<Int?>) {\n        // If empty position, return\n        if (_val(i) == null)\n            return\n        // Preorder traversal\n        if (\"pre\" == order)\n            res.add(_val(i))\n        dfs(left(i), order, res)\n        // Inorder traversal\n        if (\"in\" == order)\n            res.add(_val(i))\n        dfs(right(i), order, res)\n        // Postorder traversal\n        if (\"post\" == order)\n            res.add(_val(i))\n    }\n\n    /* Preorder traversal */\n    fun preOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"pre\", res)\n        return res\n    }\n\n    /* Inorder traversal */\n    fun inOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"in\", res)\n        return res\n    }\n\n    /* Postorder traversal */\n    fun postOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"post\", res)\n        return res\n    }\n}\n
array_binary_tree.rb
### Array representation of binary tree class ###\nclass ArrayBinaryTree\n  ### Constructor ###\n  def initialize(arr)\n    @tree = arr.to_a\n  end\n\n  ### List capacity ###\n  def size\n    @tree.length\n  end\n\n  ### Get value of node at index i ###\n  def val(i)\n    # Return nil if index out of bounds, representing empty position\n    return if i < 0 || i >= size\n\n    @tree[i]\n  end\n\n  ### Get left child index of node at index i ###\n  def left(i)\n    2 * i + 1\n  end\n\n  ### Get right child index of node at index i ###\n  def right(i)\n    2 * i + 2\n  end\n\n  ### Get parent node index of node at index i ###\n  def parent(i)\n    (i - 1) / 2\n  end\n\n  ### Level-order traversal ###\n  def level_order\n    @res = []\n\n    # Traverse array directly\n    for i in 0...size\n      @res << val(i) unless val(i).nil?\n    end\n\n    @res\n  end\n\n  ### Depth-first traversal ###\n  def dfs(i, order)\n    return if val(i).nil?\n    # Preorder traversal\n    @res << val(i) if order == :pre\n    dfs(left(i), order)\n    # Inorder traversal\n    @res << val(i) if order == :in\n    dfs(right(i), order)\n    # Postorder traversal\n    @res << val(i) if order == :post\n  end\n\n  ### Pre-order traversal ###\n  def pre_order\n    @res = []\n    dfs(0, :pre)\n    @res\n  end\n\n  ### In-order traversal ###\n  def in_order\n    @res = []\n    dfs(0, :in)\n    @res\n  end\n\n  ### Post-order traversal ###\n  def post_order\n    @res = []\n    dfs(0, :post)\n    @res\n  end\nend\n
","path":["Chapter 7. Tree","7.3   Array Representation of Binary Trees"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#733-advantages-and-limitations","level":2,"title":"7.3.3   Advantages and Limitations","text":"

The array representation of binary trees has the following advantages:

  • Arrays are stored in contiguous memory space, which is cache-friendly, allowing faster access and traversal.
  • It does not require storing pointers, which saves space.
  • It allows random access to nodes.

However, the array representation also has some limitations:

  • Array storage requires contiguous memory space, so it is not suitable for storing trees with a large amount of data.
  • Adding or removing nodes requires array insertion and deletion operations, which have lower efficiency.
  • When there are many None values in the binary tree, the proportion of node data contained in the array is low, leading to lower space utilization.
","path":["Chapter 7. Tree","7.3   Array Representation of Binary Trees"],"tags":[]},{"location":"chapter_tree/avl_tree/","level":1,"title":"7.5   Avl Tree *","text":"

In the \"Binary Search Tree\" section, we mentioned that after multiple insertion and removal operations, a binary search tree may degenerate into a linked list. In this case, the time complexity of all operations degrades from \\(O(\\log n)\\) to \\(O(n)\\).

As shown in Figure 7-24, after two node removal operations, this binary search tree will degrade into a linked list.

Figure 7-24   Degradation of an AVL tree after removing nodes

For example, in the perfect binary tree shown in Figure 7-25, after inserting two nodes, the tree will lean heavily to the left, and the time complexity of search operations will also degrade.

Figure 7-25   Degradation of an AVL tree after inserting nodes

In 1962, G. M. Adelson-Velsky and E. M. Landis proposed the AVL tree in their paper \"An algorithm for the organization of information\". The paper described in detail a series of operations ensuring that after continuously adding and removing nodes, the AVL tree does not degenerate, thus keeping the time complexity of various operations at the \\(O(\\log n)\\) level. In other words, in scenarios requiring frequent insertions, deletions, searches, and modifications, the AVL tree can always maintain efficient data operation performance, making it very valuable in applications.

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#751-common-terminology-in-avl-trees","level":2,"title":"7.5.1   Common Terminology in Avl Trees","text":"

An AVL tree is both a binary search tree and a balanced binary tree, simultaneously satisfying all the properties of these two types of binary trees, hence it is a balanced binary search tree.

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-node-height","level":3,"title":"1.   Node Height","text":"

Since the operations related to AVL trees require obtaining node heights, we need to add a height variable to the node class:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
class TreeNode:\n    \"\"\"AVL tree node\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                 # Node value\n        self.height: int = 0                # Node height\n        self.left: TreeNode | None = None   # Left child reference\n        self.right: TreeNode | None = None  # Right child reference\n
/* AVL tree node */\nstruct TreeNode {\n    int val{};          // Node value\n    int height = 0;     // Node height\n    TreeNode *left{};   // Left child\n    TreeNode *right{};  // Right child\n    TreeNode() = default;\n    explicit TreeNode(int x) : val(x){}\n};\n
/* AVL tree node */\nclass TreeNode {\n    public int val;        // Node value\n    public int height;     // Node height\n    public TreeNode left;  // Left child\n    public TreeNode right; // Right child\n    public TreeNode(int x) { val = x; }\n}\n
/* AVL tree node */\nclass TreeNode(int? x) {\n    public int? val = x;    // Node value\n    public int height;      // Node height\n    public TreeNode? left;  // Left child reference\n    public TreeNode? right; // Right child reference\n}\n
/* AVL tree node */\ntype TreeNode struct {\n    Val    int       // Node value\n    Height int       // Node height\n    Left   *TreeNode // Left child reference\n    Right  *TreeNode // Right child reference\n}\n
/* AVL tree node */\nclass TreeNode {\n    var val: Int // Node value\n    var height: Int // Node height\n    var left: TreeNode? // Left child\n    var right: TreeNode? // Right child\n\n    init(x: Int) {\n        val = x\n        height = 0\n    }\n}\n
/* AVL tree node */\nclass TreeNode {\n    val; // Node value\n    height; // Node height\n    left; // Left child pointer\n    right; // Right child pointer\n    constructor(val, left, right, height) {\n        this.val = val === undefined ? 0 : val;\n        this.height = height === undefined ? 0 : height;\n        this.left = left === undefined ? null : left;\n        this.right = right === undefined ? null : right;\n    }\n}\n
/* AVL tree node */\nclass TreeNode {\n    val: number;            // Node value\n    height: number;         // Node height\n    left: TreeNode | null;  // Left child pointer\n    right: TreeNode | null; // Right child pointer\n    constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) {\n        this.val = val === undefined ? 0 : val;\n        this.height = height === undefined ? 0 : height; \n        this.left = left === undefined ? null : left; \n        this.right = right === undefined ? null : right; \n    }\n}\n
/* AVL tree node */\nclass TreeNode {\n  int val;         // Node value\n  int height;      // Node height\n  TreeNode? left;  // Left child\n  TreeNode? right; // Right child\n  TreeNode(this.val, [this.height = 0, this.left, this.right]);\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* AVL tree node */\nstruct TreeNode {\n    val: i32,                               // Node value\n    height: i32,                            // Node height\n    left: Option<Rc<RefCell<TreeNode>>>,    // Left child\n    right: Option<Rc<RefCell<TreeNode>>>,   // Right child\n}\n\nimpl TreeNode {\n    /* Constructor */\n    fn new(val: i32) -> Rc<RefCell<Self>> {\n        Rc::new(RefCell::new(Self {\n            val,\n            height: 0,\n            left: None,\n            right: None\n        }))\n    }\n}\n
/* AVL tree node */\ntypedef struct TreeNode {\n    int val;\n    int height;\n    struct TreeNode *left;\n    struct TreeNode *right;\n} TreeNode;\n\n/* Constructor */\nTreeNode *newTreeNode(int val) {\n    TreeNode *node;\n\n    node = (TreeNode *)malloc(sizeof(TreeNode));\n    node->val = val;\n    node->height = 0;\n    node->left = NULL;\n    node->right = NULL;\n    return node;\n}\n
/* AVL tree node */\nclass TreeNode(val _val: Int) {  // Node value\n    val height: Int = 0          // Node height\n    val left: TreeNode? = null   // Left child\n    val right: TreeNode? = null  // Right child\n}\n
### AVL tree node class ###\nclass TreeNode\n  attr_accessor :val    # Node value\n  attr_accessor :height # Node height\n  attr_accessor :left   # Left child reference\n  attr_accessor :right  # Right child reference\n\n  def initialize(val)\n    @val = val\n    @height = 0\n  end\nend\n

The \"node height\" refers to the distance from that node to its farthest leaf node, i.e., the number of \"edges\" passed. It is important to note that the height of a leaf node is \\(0\\), and the height of a null node is \\(-1\\). We will create two utility functions for getting and updating the height of a node:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def height(self, node: TreeNode | None) -> int:\n    \"\"\"Get node height\"\"\"\n    # Empty node height is -1, leaf node height is 0\n    if node is not None:\n        return node.height\n    return -1\n\ndef update_height(self, node: TreeNode | None):\n    \"\"\"Update node height\"\"\"\n    # Node height equals the height of the tallest subtree + 1\n    node.height = max([self.height(node.left), self.height(node.right)]) + 1\n
avl_tree.cpp
/* Get node height */\nint height(TreeNode *node) {\n    // Empty node height is -1, leaf node height is 0\n    return node == nullptr ? -1 : node->height;\n}\n\n/* Update node height */\nvoid updateHeight(TreeNode *node) {\n    // Node height equals the height of the tallest subtree + 1\n    node->height = max(height(node->left), height(node->right)) + 1;\n}\n
avl_tree.java
/* Get node height */\nint height(TreeNode node) {\n    // Empty node height is -1, leaf node height is 0\n    return node == null ? -1 : node.height;\n}\n\n/* Update node height */\nvoid updateHeight(TreeNode node) {\n    // Node height equals the height of the tallest subtree + 1\n    node.height = Math.max(height(node.left), height(node.right)) + 1;\n}\n
avl_tree.cs
/* Get node height */\nint Height(TreeNode? node) {\n    // Empty node height is -1, leaf node height is 0\n    return node == null ? -1 : node.height;\n}\n\n/* Update node height */\nvoid UpdateHeight(TreeNode node) {\n    // Node height equals the height of the tallest subtree + 1\n    node.height = Math.Max(Height(node.left), Height(node.right)) + 1;\n}\n
avl_tree.go
/* Get node height */\nfunc (t *aVLTree) height(node *TreeNode) int {\n    // Empty node height is -1, leaf node height is 0\n    if node != nil {\n        return node.Height\n    }\n    return -1\n}\n\n/* Update node height */\nfunc (t *aVLTree) updateHeight(node *TreeNode) {\n    lh := t.height(node.Left)\n    rh := t.height(node.Right)\n    // Node height equals the height of the tallest subtree + 1\n    if lh > rh {\n        node.Height = lh + 1\n    } else {\n        node.Height = rh + 1\n    }\n}\n
avl_tree.swift
/* Get node height */\nfunc height(node: TreeNode?) -> Int {\n    // Empty node height is -1, leaf node height is 0\n    node?.height ?? -1\n}\n\n/* Update node height */\nfunc updateHeight(node: TreeNode?) {\n    // Node height equals the height of the tallest subtree + 1\n    node?.height = max(height(node: node?.left), height(node: node?.right)) + 1\n}\n
avl_tree.js
/* Get node height */\nheight(node) {\n    // Empty node height is -1, leaf node height is 0\n    return node === null ? -1 : node.height;\n}\n\n/* Update node height */\n#updateHeight(node) {\n    // Node height equals the height of the tallest subtree + 1\n    node.height =\n        Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n
avl_tree.ts
/* Get node height */\nheight(node: TreeNode): number {\n    // Empty node height is -1, leaf node height is 0\n    return node === null ? -1 : node.height;\n}\n\n/* Update node height */\nupdateHeight(node: TreeNode): void {\n    // Node height equals the height of the tallest subtree + 1\n    node.height =\n        Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n
avl_tree.dart
/* Get node height */\nint height(TreeNode? node) {\n  // Empty node height is -1, leaf node height is 0\n  return node == null ? -1 : node.height;\n}\n\n/* Update node height */\nvoid updateHeight(TreeNode? node) {\n  // Node height equals the height of the tallest subtree + 1\n  node!.height = max(height(node.left), height(node.right)) + 1;\n}\n
avl_tree.rs
/* Get node height */\nfn height(node: OptionTreeNodeRc) -> i32 {\n    // Empty node height is -1, leaf node height is 0\n    match node {\n        Some(node) => node.borrow().height,\n        None => -1,\n    }\n}\n\n/* Update node height */\nfn update_height(node: OptionTreeNodeRc) {\n    if let Some(node) = node {\n        let left = node.borrow().left.clone();\n        let right = node.borrow().right.clone();\n        // Node height equals the height of the tallest subtree + 1\n        node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1;\n    }\n}\n
avl_tree.c
/* Get node height */\nint height(TreeNode *node) {\n    // Empty node height is -1, leaf node height is 0\n    if (node != NULL) {\n        return node->height;\n    }\n    return -1;\n}\n\n/* Update node height */\nvoid updateHeight(TreeNode *node) {\n    int lh = height(node->left);\n    int rh = height(node->right);\n    // Node height equals the height of the tallest subtree + 1\n    if (lh > rh) {\n        node->height = lh + 1;\n    } else {\n        node->height = rh + 1;\n    }\n}\n
avl_tree.kt
/* Get node height */\nfun height(node: TreeNode?): Int {\n    // Empty node height is -1, leaf node height is 0\n    return node?.height ?: -1\n}\n\n/* Update node height */\nfun updateHeight(node: TreeNode?) {\n    // Node height equals the height of the tallest subtree + 1\n    node?.height = max(height(node?.left), height(node?.right)) + 1\n}\n
avl_tree.rb
### Get node height ###\ndef height(node)\n  # Empty node height is -1, leaf node height is 0\n  return node.height unless node.nil?\n\n  -1\nend\n\n### Update node height ###\ndef update_height(node)\n  # Node height equals the height of the tallest subtree + 1\n  node.height = [height(node.left), height(node.right)].max + 1\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-node-balance-factor","level":3,"title":"2.   Node Balance Factor","text":"

The balance factor of a node is defined as the height of the node's left subtree minus the height of its right subtree, and the balance factor of a null node is defined as \\(0\\). We also encapsulate the function to obtain the node's balance factor for convenient subsequent use:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def balance_factor(self, node: TreeNode | None) -> int:\n    \"\"\"Get balance factor\"\"\"\n    # Empty node balance factor is 0\n    if node is None:\n        return 0\n    # Node balance factor = left subtree height - right subtree height\n    return self.height(node.left) - self.height(node.right)\n
avl_tree.cpp
/* Get balance factor */\nint balanceFactor(TreeNode *node) {\n    // Empty node balance factor is 0\n    if (node == nullptr)\n        return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return height(node->left) - height(node->right);\n}\n
avl_tree.java
/* Get balance factor */\nint balanceFactor(TreeNode node) {\n    // Empty node balance factor is 0\n    if (node == null)\n        return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return height(node.left) - height(node.right);\n}\n
avl_tree.cs
/* Get balance factor */\nint BalanceFactor(TreeNode? node) {\n    // Empty node balance factor is 0\n    if (node == null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return Height(node.left) - Height(node.right);\n}\n
avl_tree.go
/* Get balance factor */\nfunc (t *aVLTree) balanceFactor(node *TreeNode) int {\n    // Empty node balance factor is 0\n    if node == nil {\n        return 0\n    }\n    // Node balance factor = left subtree height - right subtree height\n    return t.height(node.Left) - t.height(node.Right)\n}\n
avl_tree.swift
/* Get balance factor */\nfunc balanceFactor(node: TreeNode?) -> Int {\n    // Empty node balance factor is 0\n    guard let node = node else { return 0 }\n    // Node balance factor = left subtree height - right subtree height\n    return height(node: node.left) - height(node: node.right)\n}\n
avl_tree.js
/* Get balance factor */\nbalanceFactor(node) {\n    // Empty node balance factor is 0\n    if (node === null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return this.height(node.left) - this.height(node.right);\n}\n
avl_tree.ts
/* Get balance factor */\nbalanceFactor(node: TreeNode): number {\n    // Empty node balance factor is 0\n    if (node === null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return this.height(node.left) - this.height(node.right);\n}\n
avl_tree.dart
/* Get balance factor */\nint balanceFactor(TreeNode? node) {\n  // Empty node balance factor is 0\n  if (node == null) return 0;\n  // Node balance factor = left subtree height - right subtree height\n  return height(node.left) - height(node.right);\n}\n
avl_tree.rs
/* Get balance factor */\nfn balance_factor(node: OptionTreeNodeRc) -> i32 {\n    match node {\n        // Empty node balance factor is 0\n        None => 0,\n        // Node balance factor = left subtree height - right subtree height\n        Some(node) => {\n            Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone())\n        }\n    }\n}\n
avl_tree.c
/* Get balance factor */\nint balanceFactor(TreeNode *node) {\n    // Empty node balance factor is 0\n    if (node == NULL) {\n        return 0;\n    }\n    // Node balance factor = left subtree height - right subtree height\n    return height(node->left) - height(node->right);\n}\n
avl_tree.kt
/* Get balance factor */\nfun balanceFactor(node: TreeNode?): Int {\n    // Empty node balance factor is 0\n    if (node == null) return 0\n    // Node balance factor = left subtree height - right subtree height\n    return height(node.left) - height(node.right)\n}\n
avl_tree.rb
### Get balance factor ###\ndef balance_factor(node)\n  # Empty node balance factor is 0\n  return 0 if node.nil?\n\n  # Node balance factor = left subtree height - right subtree height\n  height(node.left) - height(node.right)\nend\n

Tip

Let the balance factor be \\(f\\), then the balance factor of any node in an AVL tree satisfies \\(-1 \\le f \\le 1\\).

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#752-rotations-in-avl-trees","level":2,"title":"7.5.2   Rotations in Avl Trees","text":"

The characteristic of AVL trees lies in the \"rotation\" operation, which can restore balance to unbalanced nodes without affecting the inorder traversal sequence of the binary tree. In other words, rotation operations can both maintain the property of a \"binary search tree\" and make the tree return to a \"balanced binary tree\".

We call nodes with a balance factor absolute value \\(> 1\\) \"unbalanced nodes\". Depending on the imbalance situation, rotation operations are divided into four types: right rotation, left rotation, left rotation then right rotation, and right rotation then left rotation. Below we describe these rotation operations in detail.

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-right-rotation","level":3,"title":"1.   Right Rotation","text":"

As shown in Figure 7-26, the value below the node is the balance factor. From bottom to top, the first unbalanced node in the binary tree is \"node 3\". We focus on the subtree with this unbalanced node as the root, denoting the node as node and its left child as child, and perform a \"right rotation\" operation. After the right rotation is completed, the subtree regains balance and still maintains the properties of a binary search tree.

<1><2><3><4>

Figure 7-26   Steps of right rotation

As shown in Figure 7-27, when the child node has a right child (denoted as grand_child), a step needs to be added in the right rotation: set grand_child as the left child of node.

Figure 7-27   Right rotation with grand_child

\"Right rotation\" is a figurative term; in practice, it is achieved by modifying node pointers, as shown in the following code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def right_rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Right rotation operation\"\"\"\n    child = node.left\n    grand_child = child.right\n    # Using child as pivot, rotate node to the right\n    child.right = node\n    node.left = grand_child\n    # Update node height\n    self.update_height(node)\n    self.update_height(child)\n    # Return root node of subtree after rotation\n    return child\n
avl_tree.cpp
/* Right rotation operation */\nTreeNode *rightRotate(TreeNode *node) {\n    TreeNode *child = node->left;\n    TreeNode *grandChild = child->right;\n    // Using child as pivot, rotate node to the right\n    child->right = node;\n    node->left = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.java
/* Right rotation operation */\nTreeNode rightRotate(TreeNode node) {\n    TreeNode child = node.left;\n    TreeNode grandChild = child.right;\n    // Using child as pivot, rotate node to the right\n    child.right = node;\n    node.left = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.cs
/* Right rotation operation */\nTreeNode? RightRotate(TreeNode? node) {\n    TreeNode? child = node?.left;\n    TreeNode? grandChild = child?.right;\n    // Using child as pivot, rotate node to the right\n    child.right = node;\n    node.left = grandChild;\n    // Update node height\n    UpdateHeight(node);\n    UpdateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.go
/* Right rotation operation */\nfunc (t *aVLTree) rightRotate(node *TreeNode) *TreeNode {\n    child := node.Left\n    grandChild := child.Right\n    // Using child as pivot, rotate node to the right\n    child.Right = node\n    node.Left = grandChild\n    // Update node height\n    t.updateHeight(node)\n    t.updateHeight(child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.swift
/* Right rotation operation */\nfunc rightRotate(node: TreeNode?) -> TreeNode? {\n    let child = node?.left\n    let grandChild = child?.right\n    // Using child as pivot, rotate node to the right\n    child?.right = node\n    node?.left = grandChild\n    // Update node height\n    updateHeight(node: node)\n    updateHeight(node: child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.js
/* Right rotation operation */\n#rightRotate(node) {\n    const child = node.left;\n    const grandChild = child.right;\n    // Using child as pivot, rotate node to the right\n    child.right = node;\n    node.left = grandChild;\n    // Update node height\n    this.#updateHeight(node);\n    this.#updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.ts
/* Right rotation operation */\nrightRotate(node: TreeNode): TreeNode {\n    const child = node.left;\n    const grandChild = child.right;\n    // Using child as pivot, rotate node to the right\n    child.right = node;\n    node.left = grandChild;\n    // Update node height\n    this.updateHeight(node);\n    this.updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.dart
/* Right rotation operation */\nTreeNode? rightRotate(TreeNode? node) {\n  TreeNode? child = node!.left;\n  TreeNode? grandChild = child!.right;\n  // Using child as pivot, rotate node to the right\n  child.right = node;\n  node.left = grandChild;\n  // Update node height\n  updateHeight(node);\n  updateHeight(child);\n  // Return root node of subtree after rotation\n  return child;\n}\n
avl_tree.rs
/* Right rotation operation */\nfn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    match node {\n        Some(node) => {\n            let child = node.borrow().left.clone().unwrap();\n            let grand_child = child.borrow().right.clone();\n            // Using child as pivot, rotate node to the right\n            child.borrow_mut().right = Some(node.clone());\n            node.borrow_mut().left = grand_child;\n            // Update node height\n            Self::update_height(Some(node));\n            Self::update_height(Some(child.clone()));\n            // Return root node of subtree after rotation\n            Some(child)\n        }\n        None => None,\n    }\n}\n
avl_tree.c
/* Right rotation operation */\nTreeNode *rightRotate(TreeNode *node) {\n    TreeNode *child, *grandChild;\n    child = node->left;\n    grandChild = child->right;\n    // Using child as pivot, rotate node to the right\n    child->right = node;\n    node->left = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.kt
/* Right rotation operation */\nfun rightRotate(node: TreeNode?): TreeNode {\n    val child = node!!.left\n    val grandChild = child!!.right\n    // Using child as pivot, rotate node to the right\n    child.right = node\n    node.left = grandChild\n    // Update node height\n    updateHeight(node)\n    updateHeight(child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.rb
### Right rotation ###\ndef right_rotate(node)\n  child = node.left\n  grand_child = child.right\n  # Using child as pivot, rotate node to the right\n  child.right = node\n  node.left = grand_child\n  # Update node height\n  update_height(node)\n  update_height(child)\n  # Return root node of subtree after rotation\n  child\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-left-rotation","level":3,"title":"2.   Left Rotation","text":"

Correspondingly, if considering the \"mirror\" of the above unbalanced binary tree, the \"left rotation\" operation shown in Figure 7-28 needs to be performed.

Figure 7-28   Left rotation operation

Similarly, as shown in Figure 7-29, when the child node has a left child (denoted as grand_child), a step needs to be added in the left rotation: set grand_child as the right child of node.

Figure 7-29   Left rotation with grand_child

It can be observed that right rotation and left rotation operations are mirror symmetric in logic, and the two imbalance cases they solve are also symmetric. Based on symmetry, we only need to replace all left in the right rotation implementation code with right, and all right with left, to obtain the left rotation implementation code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def left_rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Left rotation operation\"\"\"\n    child = node.right\n    grand_child = child.left\n    # Using child as pivot, rotate node to the left\n    child.left = node\n    node.right = grand_child\n    # Update node height\n    self.update_height(node)\n    self.update_height(child)\n    # Return root node of subtree after rotation\n    return child\n
avl_tree.cpp
/* Left rotation operation */\nTreeNode *leftRotate(TreeNode *node) {\n    TreeNode *child = node->right;\n    TreeNode *grandChild = child->left;\n    // Using child as pivot, rotate node to the left\n    child->left = node;\n    node->right = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.java
/* Left rotation operation */\nTreeNode leftRotate(TreeNode node) {\n    TreeNode child = node.right;\n    TreeNode grandChild = child.left;\n    // Using child as pivot, rotate node to the left\n    child.left = node;\n    node.right = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.cs
/* Left rotation operation */\nTreeNode? LeftRotate(TreeNode? node) {\n    TreeNode? child = node?.right;\n    TreeNode? grandChild = child?.left;\n    // Using child as pivot, rotate node to the left\n    child.left = node;\n    node.right = grandChild;\n    // Update node height\n    UpdateHeight(node);\n    UpdateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.go
/* Left rotation operation */\nfunc (t *aVLTree) leftRotate(node *TreeNode) *TreeNode {\n    child := node.Right\n    grandChild := child.Left\n    // Using child as pivot, rotate node to the left\n    child.Left = node\n    node.Right = grandChild\n    // Update node height\n    t.updateHeight(node)\n    t.updateHeight(child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.swift
/* Left rotation operation */\nfunc leftRotate(node: TreeNode?) -> TreeNode? {\n    let child = node?.right\n    let grandChild = child?.left\n    // Using child as pivot, rotate node to the left\n    child?.left = node\n    node?.right = grandChild\n    // Update node height\n    updateHeight(node: node)\n    updateHeight(node: child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.js
/* Left rotation operation */\n#leftRotate(node) {\n    const child = node.right;\n    const grandChild = child.left;\n    // Using child as pivot, rotate node to the left\n    child.left = node;\n    node.right = grandChild;\n    // Update node height\n    this.#updateHeight(node);\n    this.#updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.ts
/* Left rotation operation */\nleftRotate(node: TreeNode): TreeNode {\n    const child = node.right;\n    const grandChild = child.left;\n    // Using child as pivot, rotate node to the left\n    child.left = node;\n    node.right = grandChild;\n    // Update node height\n    this.updateHeight(node);\n    this.updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.dart
/* Left rotation operation */\nTreeNode? leftRotate(TreeNode? node) {\n  TreeNode? child = node!.right;\n  TreeNode? grandChild = child!.left;\n  // Using child as pivot, rotate node to the left\n  child.left = node;\n  node.right = grandChild;\n  // Update node height\n  updateHeight(node);\n  updateHeight(child);\n  // Return root node of subtree after rotation\n  return child;\n}\n
avl_tree.rs
/* Left rotation operation */\nfn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    match node {\n        Some(node) => {\n            let child = node.borrow().right.clone().unwrap();\n            let grand_child = child.borrow().left.clone();\n            // Using child as pivot, rotate node to the left\n            child.borrow_mut().left = Some(node.clone());\n            node.borrow_mut().right = grand_child;\n            // Update node height\n            Self::update_height(Some(node));\n            Self::update_height(Some(child.clone()));\n            // Return root node of subtree after rotation\n            Some(child)\n        }\n        None => None,\n    }\n}\n
avl_tree.c
/* Left rotation operation */\nTreeNode *leftRotate(TreeNode *node) {\n    TreeNode *child, *grandChild;\n    child = node->right;\n    grandChild = child->left;\n    // Using child as pivot, rotate node to the left\n    child->left = node;\n    node->right = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.kt
/* Left rotation operation */\nfun leftRotate(node: TreeNode?): TreeNode {\n    val child = node!!.right\n    val grandChild = child!!.left\n    // Using child as pivot, rotate node to the left\n    child.left = node\n    node.right = grandChild\n    // Update node height\n    updateHeight(node)\n    updateHeight(child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.rb
### Left rotation ###\ndef left_rotate(node)\n  child = node.right\n  grand_child = child.left\n  # Using child as pivot, rotate node to the left\n  child.left = node\n  node.right = grand_child\n  # Update node height\n  update_height(node)\n  update_height(child)\n  # Return root node of subtree after rotation\n  child\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#3-left-rotation-then-right-rotation","level":3,"title":"3.   Left Rotation Then Right Rotation","text":"

For the unbalanced node 3 in Figure 7-30, using either left rotation or right rotation alone cannot restore the subtree to balance. In this case, a \"left rotation\" needs to be performed on child first, followed by a \"right rotation\" on node.

Figure 7-30   Left-right rotation

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#4-right-rotation-then-left-rotation","level":3,"title":"4.   Right Rotation Then Left Rotation","text":"

As shown in Figure 7-31, for the mirror case of the above unbalanced binary tree, a \"right rotation\" needs to be performed on child first, then a \"left rotation\" on node.

Figure 7-31   Right-left rotation

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#5-choice-of-rotation","level":3,"title":"5.   Choice of Rotation","text":"

The four imbalances shown in Figure 7-32 correspond one-to-one with the above cases, requiring right rotation, left rotation then right rotation, right rotation then left rotation, and left rotation operations respectively.

Figure 7-32   The four rotation cases of AVL tree

As shown in Table 7-3, we determine which case the unbalanced node belongs to by judging the signs of the balance factor of the unbalanced node and the balance factor of its taller-side child node.

Table 7-3   Conditions for Choosing Among the Four Rotation Cases

Balance factor of the unbalanced node Balance factor of the child node Rotation method to apply \\(> 1\\) (left-leaning tree) \\(\\geq 0\\) Right rotation \\(> 1\\) (left-leaning tree) \\(<0\\) Left rotation then right rotation \\(< -1\\) (right-leaning tree) \\(\\leq 0\\) Left rotation \\(< -1\\) (right-leaning tree) \\(>0\\) Right rotation then left rotation

For ease of use, we encapsulate the rotation operations into a function. With this function, we can perform rotations for various imbalance situations, restoring balance to unbalanced nodes. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Perform rotation operation to restore balance to this subtree\"\"\"\n    # Get balance factor of node\n    balance_factor = self.balance_factor(node)\n    # Left-leaning tree\n    if balance_factor > 1:\n        if self.balance_factor(node.left) >= 0:\n            # Right rotation\n            return self.right_rotate(node)\n        else:\n            # First left rotation then right rotation\n            node.left = self.left_rotate(node.left)\n            return self.right_rotate(node)\n    # Right-leaning tree\n    elif balance_factor < -1:\n        if self.balance_factor(node.right) <= 0:\n            # Left rotation\n            return self.left_rotate(node)\n        else:\n            # First right rotation then left rotation\n            node.right = self.right_rotate(node.right)\n            return self.left_rotate(node)\n    # Balanced tree, no rotation needed, return directly\n    return node\n
avl_tree.cpp
/* Perform rotation operation to restore balance to this subtree */\nTreeNode *rotate(TreeNode *node) {\n    // Get balance factor of node\n    int _balanceFactor = balanceFactor(node);\n    // Left-leaning tree\n    if (_balanceFactor > 1) {\n        if (balanceFactor(node->left) >= 0) {\n            // Right rotation\n            return rightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node->left = leftRotate(node->left);\n            return rightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (_balanceFactor < -1) {\n        if (balanceFactor(node->right) <= 0) {\n            // Left rotation\n            return leftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node->right = rightRotate(node->right);\n            return leftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.java
/* Perform rotation operation to restore balance to this subtree */\nTreeNode rotate(TreeNode node) {\n    // Get balance factor of node\n    int balanceFactor = balanceFactor(node);\n    // Left-leaning tree\n    if (balanceFactor > 1) {\n        if (balanceFactor(node.left) >= 0) {\n            // Right rotation\n            return rightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node.left = leftRotate(node.left);\n            return rightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (balanceFactor < -1) {\n        if (balanceFactor(node.right) <= 0) {\n            // Left rotation\n            return leftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node.right = rightRotate(node.right);\n            return leftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.cs
/* Perform rotation operation to restore balance to this subtree */\nTreeNode? Rotate(TreeNode? node) {\n    // Get balance factor of node\n    int balanceFactorInt = BalanceFactor(node);\n    // Left-leaning tree\n    if (balanceFactorInt > 1) {\n        if (BalanceFactor(node?.left) >= 0) {\n            // Right rotation\n            return RightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node!.left = LeftRotate(node!.left);\n            return RightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (balanceFactorInt < -1) {\n        if (BalanceFactor(node?.right) <= 0) {\n            // Left rotation\n            return LeftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node!.right = RightRotate(node!.right);\n            return LeftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.go
/* Perform rotation operation to restore balance to this subtree */\nfunc (t *aVLTree) rotate(node *TreeNode) *TreeNode {\n    // Get balance factor of node\n    // Go recommends short variables, here bf refers to t.balanceFactor\n    bf := t.balanceFactor(node)\n    // Left-leaning tree\n    if bf > 1 {\n        if t.balanceFactor(node.Left) >= 0 {\n            // Right rotation\n            return t.rightRotate(node)\n        } else {\n            // First left rotation then right rotation\n            node.Left = t.leftRotate(node.Left)\n            return t.rightRotate(node)\n        }\n    }\n    // Right-leaning tree\n    if bf < -1 {\n        if t.balanceFactor(node.Right) <= 0 {\n            // Left rotation\n            return t.leftRotate(node)\n        } else {\n            // First right rotation then left rotation\n            node.Right = t.rightRotate(node.Right)\n            return t.leftRotate(node)\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node\n}\n
avl_tree.swift
/* Perform rotation operation to restore balance to this subtree */\nfunc rotate(node: TreeNode?) -> TreeNode? {\n    // Get balance factor of node\n    let balanceFactor = balanceFactor(node: node)\n    // Left-leaning tree\n    if balanceFactor > 1 {\n        if self.balanceFactor(node: node?.left) >= 0 {\n            // Right rotation\n            return rightRotate(node: node)\n        } else {\n            // First left rotation then right rotation\n            node?.left = leftRotate(node: node?.left)\n            return rightRotate(node: node)\n        }\n    }\n    // Right-leaning tree\n    if balanceFactor < -1 {\n        if self.balanceFactor(node: node?.right) <= 0 {\n            // Left rotation\n            return leftRotate(node: node)\n        } else {\n            // First right rotation then left rotation\n            node?.right = rightRotate(node: node?.right)\n            return leftRotate(node: node)\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node\n}\n
avl_tree.js
/* Perform rotation operation to restore balance to this subtree */\n#rotate(node) {\n    // Get balance factor of node\n    const balanceFactor = this.balanceFactor(node);\n    // Left-leaning tree\n    if (balanceFactor > 1) {\n        if (this.balanceFactor(node.left) >= 0) {\n            // Right rotation\n            return this.#rightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node.left = this.#leftRotate(node.left);\n            return this.#rightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (balanceFactor < -1) {\n        if (this.balanceFactor(node.right) <= 0) {\n            // Left rotation\n            return this.#leftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node.right = this.#rightRotate(node.right);\n            return this.#leftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.ts
/* Perform rotation operation to restore balance to this subtree */\nrotate(node: TreeNode): TreeNode {\n    // Get balance factor of node\n    const balanceFactor = this.balanceFactor(node);\n    // Left-leaning tree\n    if (balanceFactor > 1) {\n        if (this.balanceFactor(node.left) >= 0) {\n            // Right rotation\n            return this.rightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node.left = this.leftRotate(node.left);\n            return this.rightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (balanceFactor < -1) {\n        if (this.balanceFactor(node.right) <= 0) {\n            // Left rotation\n            return this.leftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node.right = this.rightRotate(node.right);\n            return this.leftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.dart
/* Perform rotation operation to restore balance to this subtree */\nTreeNode? rotate(TreeNode? node) {\n  // Get balance factor of node\n  int factor = balanceFactor(node);\n  // Left-leaning tree\n  if (factor > 1) {\n    if (balanceFactor(node!.left) >= 0) {\n      // Right rotation\n      return rightRotate(node);\n    } else {\n      // First left rotation then right rotation\n      node.left = leftRotate(node.left);\n      return rightRotate(node);\n    }\n  }\n  // Right-leaning tree\n  if (factor < -1) {\n    if (balanceFactor(node!.right) <= 0) {\n      // Left rotation\n      return leftRotate(node);\n    } else {\n      // First right rotation then left rotation\n      node.right = rightRotate(node.right);\n      return leftRotate(node);\n    }\n  }\n  // Balanced tree, no rotation needed, return directly\n  return node;\n}\n
avl_tree.rs
/* Perform rotation operation to restore balance to this subtree */\nfn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    // Get balance factor of node\n    let balance_factor = Self::balance_factor(node.clone());\n    // Left-leaning tree\n    if balance_factor > 1 {\n        let node = node.unwrap();\n        if Self::balance_factor(node.borrow().left.clone()) >= 0 {\n            // Right rotation\n            Self::right_rotate(Some(node))\n        } else {\n            // First left rotation then right rotation\n            let left = node.borrow().left.clone();\n            node.borrow_mut().left = Self::left_rotate(left);\n            Self::right_rotate(Some(node))\n        }\n    }\n    // Right-leaning tree\n    else if balance_factor < -1 {\n        let node = node.unwrap();\n        if Self::balance_factor(node.borrow().right.clone()) <= 0 {\n            // Left rotation\n            Self::left_rotate(Some(node))\n        } else {\n            // First right rotation then left rotation\n            let right = node.borrow().right.clone();\n            node.borrow_mut().right = Self::right_rotate(right);\n            Self::left_rotate(Some(node))\n        }\n    } else {\n        // Balanced tree, no rotation needed, return directly\n        node\n    }\n}\n
avl_tree.c
/* Perform rotation operation to restore balance to this subtree */\nTreeNode *rotate(TreeNode *node) {\n    // Get balance factor of node\n    int bf = balanceFactor(node);\n    // Left-leaning tree\n    if (bf > 1) {\n        if (balanceFactor(node->left) >= 0) {\n            // Right rotation\n            return rightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node->left = leftRotate(node->left);\n            return rightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (bf < -1) {\n        if (balanceFactor(node->right) <= 0) {\n            // Left rotation\n            return leftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node->right = rightRotate(node->right);\n            return leftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.kt
/* Perform rotation operation to restore balance to this subtree */\nfun rotate(node: TreeNode): TreeNode {\n    // Get balance factor of node\n    val balanceFactor = balanceFactor(node)\n    // Left-leaning tree\n    if (balanceFactor > 1) {\n        if (balanceFactor(node.left) >= 0) {\n            // Right rotation\n            return rightRotate(node)\n        } else {\n            // First left rotation then right rotation\n            node.left = leftRotate(node.left)\n            return rightRotate(node)\n        }\n    }\n    // Right-leaning tree\n    if (balanceFactor < -1) {\n        if (balanceFactor(node.right) <= 0) {\n            // Left rotation\n            return leftRotate(node)\n        } else {\n            // First right rotation then left rotation\n            node.right = rightRotate(node.right)\n            return leftRotate(node)\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node\n}\n
avl_tree.rb
### Perform rotation to rebalance subtree ###\ndef rotate(node)\n  # Get balance factor of node\n  balance_factor = balance_factor(node)\n  # Left-heavy tree\n  if balance_factor > 1\n    if balance_factor(node.left) >= 0\n      # Right rotation\n      return right_rotate(node)\n    else\n      # First left rotation then right rotation\n      node.left = left_rotate(node.left)\n      return right_rotate(node)\n    end\n  # Right-heavy tree\n  elsif balance_factor < -1\n    if balance_factor(node.right) <= 0\n      # Left rotation\n      return left_rotate(node)\n    else\n      # First right rotation then left rotation\n      node.right = right_rotate(node.right)\n      return left_rotate(node)\n    end\n  end\n  # Balanced tree, no rotation needed, return directly\n  node\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#753-common-operations-in-avl-trees","level":2,"title":"7.5.3   Common Operations in Avl Trees","text":"","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-node-insertion","level":3,"title":"1.   Node Insertion","text":"

The node insertion operation in AVL trees is similar in principle to that in binary search trees. The only difference is that after inserting a node in an AVL tree, a series of unbalanced nodes may appear on the path from that node to the root. Therefore, we need to start from this node and perform rotation operations from bottom to top, restoring balance to all unbalanced nodes. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def insert(self, val):\n    \"\"\"Insert node\"\"\"\n    self._root = self.insert_helper(self._root, val)\n\ndef insert_helper(self, node: TreeNode | None, val: int) -> TreeNode:\n    \"\"\"Recursively insert node (helper method)\"\"\"\n    if node is None:\n        return TreeNode(val)\n    # 1. Find insertion position and insert node\n    if val < node.val:\n        node.left = self.insert_helper(node.left, val)\n    elif val > node.val:\n        node.right = self.insert_helper(node.right, val)\n    else:\n        # Duplicate node not inserted, return directly\n        return node\n    # Update node height\n    self.update_height(node)\n    # 2. Perform rotation operation to restore balance to this subtree\n    return self.rotate(node)\n
avl_tree.cpp
/* Insert node */\nvoid insert(int val) {\n    root = insertHelper(root, val);\n}\n\n/* Recursively insert node (helper method) */\nTreeNode *insertHelper(TreeNode *node, int val) {\n    if (node == nullptr)\n        return new TreeNode(val);\n    /* 1. Find insertion position and insert node */\n    if (val < node->val)\n        node->left = insertHelper(node->left, val);\n    else if (val > node->val)\n        node->right = insertHelper(node->right, val);\n    else\n        return node;    // Duplicate node not inserted, return directly\n    updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.java
/* Insert node */\nvoid insert(int val) {\n    root = insertHelper(root, val);\n}\n\n/* Recursively insert node (helper method) */\nTreeNode insertHelper(TreeNode node, int val) {\n    if (node == null)\n        return new TreeNode(val);\n    /* 1. Find insertion position and insert node */\n    if (val < node.val)\n        node.left = insertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = insertHelper(node.right, val);\n    else\n        return node; // Duplicate node not inserted, return directly\n    updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.cs
/* Insert node */\nvoid Insert(int val) {\n    root = InsertHelper(root, val);\n}\n\n/* Recursively insert node (helper method) */\nTreeNode? InsertHelper(TreeNode? node, int val) {\n    if (node == null) return new TreeNode(val);\n    /* 1. Find insertion position and insert node */\n    if (val < node.val)\n        node.left = InsertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = InsertHelper(node.right, val);\n    else\n        return node;     // Duplicate node not inserted, return directly\n    UpdateHeight(node);  // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = Rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.go
/* Insert node */\nfunc (t *aVLTree) insert(val int) {\n    t.root = t.insertHelper(t.root, val)\n}\n\n/* Recursively insert node (helper function) */\nfunc (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode {\n    if node == nil {\n        return NewTreeNode(val)\n    }\n    /* 1. Find insertion position and insert node */\n    if val < node.Val.(int) {\n        node.Left = t.insertHelper(node.Left, val)\n    } else if val > node.Val.(int) {\n        node.Right = t.insertHelper(node.Right, val)\n    } else {\n        // Duplicate node not inserted, return directly\n        return node\n    }\n    // Update node height\n    t.updateHeight(node)\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = t.rotate(node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.swift
/* Insert node */\nfunc insert(val: Int) {\n    root = insertHelper(node: root, val: val)\n}\n\n/* Recursively insert node (helper method) */\nfunc insertHelper(node: TreeNode?, val: Int) -> TreeNode? {\n    var node = node\n    if node == nil {\n        return TreeNode(x: val)\n    }\n    /* 1. Find insertion position and insert node */\n    if val < node!.val {\n        node?.left = insertHelper(node: node?.left, val: val)\n    } else if val > node!.val {\n        node?.right = insertHelper(node: node?.right, val: val)\n    } else {\n        return node // Duplicate node not inserted, return directly\n    }\n    updateHeight(node: node) // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node: node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.js
/* Insert node */\ninsert(val) {\n    this.root = this.#insertHelper(this.root, val);\n}\n\n/* Recursively insert node (helper method) */\n#insertHelper(node, val) {\n    if (node === null) return new TreeNode(val);\n    /* 1. Find insertion position and insert node */\n    if (val < node.val) node.left = this.#insertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = this.#insertHelper(node.right, val);\n    else return node; // Duplicate node not inserted, return directly\n    this.#updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = this.#rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.ts
/* Insert node */\ninsert(val: number): void {\n    this.root = this.insertHelper(this.root, val);\n}\n\n/* Recursively insert node (helper method) */\ninsertHelper(node: TreeNode, val: number): TreeNode {\n    if (node === null) return new TreeNode(val);\n    /* 1. Find insertion position and insert node */\n    if (val < node.val) {\n        node.left = this.insertHelper(node.left, val);\n    } else if (val > node.val) {\n        node.right = this.insertHelper(node.right, val);\n    } else {\n        return node; // Duplicate node not inserted, return directly\n    }\n    this.updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = this.rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.dart
/* Insert node */\nvoid insert(int val) {\n  root = insertHelper(root, val);\n}\n\n/* Recursively insert node (helper method) */\nTreeNode? insertHelper(TreeNode? node, int val) {\n  if (node == null) return TreeNode(val);\n  /* 1. Find insertion position and insert node */\n  if (val < node.val)\n    node.left = insertHelper(node.left, val);\n  else if (val > node.val)\n    node.right = insertHelper(node.right, val);\n  else\n    return node; // Duplicate node not inserted, return directly\n  updateHeight(node); // Update node height\n  /* 2. Perform rotation operation to restore balance to this subtree */\n  node = rotate(node);\n  // Return root node of subtree\n  return node;\n}\n
avl_tree.rs
/* Insert node */\nfn insert(&mut self, val: i32) {\n    self.root = Self::insert_helper(self.root.clone(), val);\n}\n\n/* Recursively insert node (helper method) */\nfn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc {\n    match node {\n        Some(mut node) => {\n            /* 1. Find insertion position and insert node */\n            match {\n                let node_val = node.borrow().val;\n                node_val\n            }\n            .cmp(&val)\n            {\n                Ordering::Greater => {\n                    let left = node.borrow().left.clone();\n                    node.borrow_mut().left = Self::insert_helper(left, val);\n                }\n                Ordering::Less => {\n                    let right = node.borrow().right.clone();\n                    node.borrow_mut().right = Self::insert_helper(right, val);\n                }\n                Ordering::Equal => {\n                    return Some(node); // Duplicate node not inserted, return directly\n                }\n            }\n            Self::update_height(Some(node.clone())); // Update node height\n\n            /* 2. Perform rotation operation to restore balance to this subtree */\n            node = Self::rotate(Some(node)).unwrap();\n            // Return root node of subtree\n            Some(node)\n        }\n        None => Some(TreeNode::new(val)),\n    }\n}\n
avl_tree.c
/* Insert node */\nvoid insert(AVLTree *tree, int val) {\n    tree->root = insertHelper(tree->root, val);\n}\n\n/* Recursively insert node (helper function) */\nTreeNode *insertHelper(TreeNode *node, int val) {\n    if (node == NULL) {\n        return newTreeNode(val);\n    }\n    /* 1. Find insertion position and insert node */\n    if (val < node->val) {\n        node->left = insertHelper(node->left, val);\n    } else if (val > node->val) {\n        node->right = insertHelper(node->right, val);\n    } else {\n        // Duplicate node not inserted, return directly\n        return node;\n    }\n    // Update node height\n    updateHeight(node);\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.kt
/* Insert node */\nfun insert(_val: Int) {\n    root = insertHelper(root, _val)\n}\n\n/* Recursively insert node (helper method) */\nfun insertHelper(n: TreeNode?, _val: Int): TreeNode {\n    if (n == null)\n        return TreeNode(_val)\n    var node = n\n    /* 1. Find insertion position and insert node */\n    if (_val < node._val)\n        node.left = insertHelper(node.left, _val)\n    else if (_val > node._val)\n        node.right = insertHelper(node.right, _val)\n    else\n        return node // Duplicate node not inserted, return directly\n    updateHeight(node) // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.rb
### Insert node ###\ndef insert(val)\n  @root = insert_helper(@root, val)\nend\n\n### Recursively insert node (helper method) ###\ndef insert_helper(node, val)\n  return TreeNode.new(val) if node.nil?\n  # 1. Find insertion position and insert node\n  if val < node.val\n    node.left = insert_helper(node.left, val)\n  elsif val > node.val\n    node.right = insert_helper(node.right, val)\n  else\n    # Duplicate node not inserted, return directly\n    return node\n  end\n  # Update node height\n  update_height(node)\n  # 2. Perform rotation operation to restore balance to this subtree\n  rotate(node)\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-node-removal","level":3,"title":"2.   Node Removal","text":"

Similarly, on the basis of the binary search tree's node removal method, rotation operations need to be performed from bottom to top to restore balance to all unbalanced nodes. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def remove(self, val: int):\n    \"\"\"Delete node\"\"\"\n    self._root = self.remove_helper(self._root, val)\n\ndef remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None:\n    \"\"\"Recursively delete node (helper method)\"\"\"\n    if node is None:\n        return None\n    # 1. Find node and delete\n    if val < node.val:\n        node.left = self.remove_helper(node.left, val)\n    elif val > node.val:\n        node.right = self.remove_helper(node.right, val)\n    else:\n        if node.left is None or node.right is None:\n            child = node.left or node.right\n            # Number of child nodes = 0, delete node directly and return\n            if child is None:\n                return None\n            # Number of child nodes = 1, delete node directly\n            else:\n                node = child\n        else:\n            # Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            temp = node.right\n            while temp.left is not None:\n                temp = temp.left\n            node.right = self.remove_helper(node.right, temp.val)\n            node.val = temp.val\n    # Update node height\n    self.update_height(node)\n    # 2. Perform rotation operation to restore balance to this subtree\n    return self.rotate(node)\n
avl_tree.cpp
/* Remove node */\nvoid remove(int val) {\n    root = removeHelper(root, val);\n}\n\n/* Recursively delete node (helper method) */\nTreeNode *removeHelper(TreeNode *node, int val) {\n    if (node == nullptr)\n        return nullptr;\n    /* 1. Find node and delete */\n    if (val < node->val)\n        node->left = removeHelper(node->left, val);\n    else if (val > node->val)\n        node->right = removeHelper(node->right, val);\n    else {\n        if (node->left == nullptr || node->right == nullptr) {\n            TreeNode *child = node->left != nullptr ? node->left : node->right;\n            // Number of child nodes = 0, delete node directly and return\n            if (child == nullptr) {\n                delete node;\n                return nullptr;\n            }\n            // Number of child nodes = 1, delete node directly\n            else {\n                delete node;\n                node = child;\n            }\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            TreeNode *temp = node->right;\n            while (temp->left != nullptr) {\n                temp = temp->left;\n            }\n            int tempVal = temp->val;\n            node->right = removeHelper(node->right, temp->val);\n            node->val = tempVal;\n        }\n    }\n    updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.java
/* Remove node */\nvoid remove(int val) {\n    root = removeHelper(root, val);\n}\n\n/* Recursively delete node (helper method) */\nTreeNode removeHelper(TreeNode node, int val) {\n    if (node == null)\n        return null;\n    /* 1. Find node and delete */\n    if (val < node.val)\n        node.left = removeHelper(node.left, val);\n    else if (val > node.val)\n        node.right = removeHelper(node.right, val);\n    else {\n        if (node.left == null || node.right == null) {\n            TreeNode child = node.left != null ? node.left : node.right;\n            // Number of child nodes = 0, delete node directly and return\n            if (child == null)\n                return null;\n            // Number of child nodes = 1, delete node directly\n            else\n                node = child;\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            TreeNode temp = node.right;\n            while (temp.left != null) {\n                temp = temp.left;\n            }\n            node.right = removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.cs
/* Remove node */\nvoid Remove(int val) {\n    root = RemoveHelper(root, val);\n}\n\n/* Recursively delete node (helper method) */\nTreeNode? RemoveHelper(TreeNode? node, int val) {\n    if (node == null) return null;\n    /* 1. Find node and delete */\n    if (val < node.val)\n        node.left = RemoveHelper(node.left, val);\n    else if (val > node.val)\n        node.right = RemoveHelper(node.right, val);\n    else {\n        if (node.left == null || node.right == null) {\n            TreeNode? child = node.left ?? node.right;\n            // Number of child nodes = 0, delete node directly and return\n            if (child == null)\n                return null;\n            // Number of child nodes = 1, delete node directly\n            else\n                node = child;\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            TreeNode? temp = node.right;\n            while (temp.left != null) {\n                temp = temp.left;\n            }\n            node.right = RemoveHelper(node.right, temp.val!.Value);\n            node.val = temp.val;\n        }\n    }\n    UpdateHeight(node);  // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = Rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.go
/* Remove node */\nfunc (t *aVLTree) remove(val int) {\n    t.root = t.removeHelper(t.root, val)\n}\n\n/* Recursively remove node (helper function) */\nfunc (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode {\n    if node == nil {\n        return nil\n    }\n    /* 1. Find node and delete */\n    if val < node.Val.(int) {\n        node.Left = t.removeHelper(node.Left, val)\n    } else if val > node.Val.(int) {\n        node.Right = t.removeHelper(node.Right, val)\n    } else {\n        if node.Left == nil || node.Right == nil {\n            child := node.Left\n            if node.Right != nil {\n                child = node.Right\n            }\n            if child == nil {\n                // Number of child nodes = 0, delete node directly and return\n                return nil\n            } else {\n                // Number of child nodes = 1, delete node directly\n                node = child\n            }\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            temp := node.Right\n            for temp.Left != nil {\n                temp = temp.Left\n            }\n            node.Right = t.removeHelper(node.Right, temp.Val.(int))\n            node.Val = temp.Val\n        }\n    }\n    // Update node height\n    t.updateHeight(node)\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = t.rotate(node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.swift
/* Remove node */\nfunc remove(val: Int) {\n    root = removeHelper(node: root, val: val)\n}\n\n/* Recursively delete node (helper method) */\nfunc removeHelper(node: TreeNode?, val: Int) -> TreeNode? {\n    var node = node\n    if node == nil {\n        return nil\n    }\n    /* 1. Find node and delete */\n    if val < node!.val {\n        node?.left = removeHelper(node: node?.left, val: val)\n    } else if val > node!.val {\n        node?.right = removeHelper(node: node?.right, val: val)\n    } else {\n        if node?.left == nil || node?.right == nil {\n            let child = node?.left ?? node?.right\n            // Number of child nodes = 0, delete node directly and return\n            if child == nil {\n                return nil\n            }\n            // Number of child nodes = 1, delete node directly\n            else {\n                node = child\n            }\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            var temp = node?.right\n            while temp?.left != nil {\n                temp = temp?.left\n            }\n            node?.right = removeHelper(node: node?.right, val: temp!.val)\n            node?.val = temp!.val\n        }\n    }\n    updateHeight(node: node) // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node: node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.js
/* Remove node */\nremove(val) {\n    this.root = this.#removeHelper(this.root, val);\n}\n\n/* Recursively delete node (helper method) */\n#removeHelper(node, val) {\n    if (node === null) return null;\n    /* 1. Find node and delete */\n    if (val < node.val) node.left = this.#removeHelper(node.left, val);\n    else if (val > node.val)\n        node.right = this.#removeHelper(node.right, val);\n    else {\n        if (node.left === null || node.right === null) {\n            const child = node.left !== null ? node.left : node.right;\n            // Number of child nodes = 0, delete node directly and return\n            if (child === null) return null;\n            // Number of child nodes = 1, delete node directly\n            else node = child;\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            let temp = node.right;\n            while (temp.left !== null) {\n                temp = temp.left;\n            }\n            node.right = this.#removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    this.#updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = this.#rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.ts
/* Remove node */\nremove(val: number): void {\n    this.root = this.removeHelper(this.root, val);\n}\n\n/* Recursively delete node (helper method) */\nremoveHelper(node: TreeNode, val: number): TreeNode {\n    if (node === null) return null;\n    /* 1. Find node and delete */\n    if (val < node.val) {\n        node.left = this.removeHelper(node.left, val);\n    } else if (val > node.val) {\n        node.right = this.removeHelper(node.right, val);\n    } else {\n        if (node.left === null || node.right === null) {\n            const child = node.left !== null ? node.left : node.right;\n            // Number of child nodes = 0, delete node directly and return\n            if (child === null) {\n                return null;\n            } else {\n                // Number of child nodes = 1, delete node directly\n                node = child;\n            }\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            let temp = node.right;\n            while (temp.left !== null) {\n                temp = temp.left;\n            }\n            node.right = this.removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    this.updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = this.rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.dart
/* Remove node */\nvoid remove(int val) {\n  root = removeHelper(root, val);\n}\n\n/* Recursively delete node (helper method) */\nTreeNode? removeHelper(TreeNode? node, int val) {\n  if (node == null) return null;\n  /* 1. Find node and delete */\n  if (val < node.val)\n    node.left = removeHelper(node.left, val);\n  else if (val > node.val)\n    node.right = removeHelper(node.right, val);\n  else {\n    if (node.left == null || node.right == null) {\n      TreeNode? child = node.left ?? node.right;\n      // Number of child nodes = 0, delete node directly and return\n      if (child == null)\n        return null;\n      // Number of child nodes = 1, delete node directly\n      else\n        node = child;\n    } else {\n      // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n      TreeNode? temp = node.right;\n      while (temp!.left != null) {\n        temp = temp.left;\n      }\n      node.right = removeHelper(node.right, temp.val);\n      node.val = temp.val;\n    }\n  }\n  updateHeight(node); // Update node height\n  /* 2. Perform rotation operation to restore balance to this subtree */\n  node = rotate(node);\n  // Return root node of subtree\n  return node;\n}\n
avl_tree.rs
/* Remove node */\nfn remove(&self, val: i32) {\n    Self::remove_helper(self.root.clone(), val);\n}\n\n/* Recursively delete node (helper method) */\nfn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc {\n    match node {\n        Some(mut node) => {\n            /* 1. Find node and delete */\n            if val < node.borrow().val {\n                let left = node.borrow().left.clone();\n                node.borrow_mut().left = Self::remove_helper(left, val);\n            } else if val > node.borrow().val {\n                let right = node.borrow().right.clone();\n                node.borrow_mut().right = Self::remove_helper(right, val);\n            } else if node.borrow().left.is_none() || node.borrow().right.is_none() {\n                let child = if node.borrow().left.is_some() {\n                    node.borrow().left.clone()\n                } else {\n                    node.borrow().right.clone()\n                };\n                match child {\n                    // Number of child nodes = 0, delete node directly and return\n                    None => {\n                        return None;\n                    }\n                    // Number of child nodes = 1, delete node directly\n                    Some(child) => node = child,\n                }\n            } else {\n                // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n                let mut temp = node.borrow().right.clone().unwrap();\n                loop {\n                    let temp_left = temp.borrow().left.clone();\n                    if temp_left.is_none() {\n                        break;\n                    }\n                    temp = temp_left.unwrap();\n                }\n                let right = node.borrow().right.clone();\n                node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val);\n                node.borrow_mut().val = temp.borrow().val;\n            }\n            Self::update_height(Some(node.clone())); // Update node height\n\n            /* 2. Perform rotation operation to restore balance to this subtree */\n            node = Self::rotate(Some(node)).unwrap();\n            // Return root node of subtree\n            Some(node)\n        }\n        None => None,\n    }\n}\n
avl_tree.c
/* Remove node */\n// Cannot use remove keyword here due to stdio.h inclusion\nvoid removeItem(AVLTree *tree, int val) {\n    TreeNode *root = removeHelper(tree->root, val);\n}\n\n/* Recursively remove node (helper function) */\nTreeNode *removeHelper(TreeNode *node, int val) {\n    TreeNode *child, *grandChild;\n    if (node == NULL) {\n        return NULL;\n    }\n    /* 1. Find node and delete */\n    if (val < node->val) {\n        node->left = removeHelper(node->left, val);\n    } else if (val > node->val) {\n        node->right = removeHelper(node->right, val);\n    } else {\n        if (node->left == NULL || node->right == NULL) {\n            child = node->left;\n            if (node->right != NULL) {\n                child = node->right;\n            }\n            // Number of child nodes = 0, delete node directly and return\n            if (child == NULL) {\n                return NULL;\n            } else {\n                // Number of child nodes = 1, delete node directly\n                node = child;\n            }\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            TreeNode *temp = node->right;\n            while (temp->left != NULL) {\n                temp = temp->left;\n            }\n            int tempVal = temp->val;\n            node->right = removeHelper(node->right, temp->val);\n            node->val = tempVal;\n        }\n    }\n    // Update node height\n    updateHeight(node);\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.kt
/* Remove node */\nfun remove(_val: Int) {\n    root = removeHelper(root, _val)\n}\n\n/* Recursively delete node (helper method) */\nfun removeHelper(n: TreeNode?, _val: Int): TreeNode? {\n    var node = n ?: return null\n    /* 1. Find node and delete */\n    if (_val < node._val)\n        node.left = removeHelper(node.left, _val)\n    else if (_val > node._val)\n        node.right = removeHelper(node.right, _val)\n    else {\n        if (node.left == null || node.right == null) {\n            val child = if (node.left != null)\n                node.left\n            else\n                node.right\n            // Number of child nodes = 0, delete node directly and return\n            if (child == null)\n                return null\n            // Number of child nodes = 1, delete node directly\n            else\n                node = child\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            var temp = node.right\n            while (temp!!.left != null) {\n                temp = temp.left\n            }\n            node.right = removeHelper(node.right, temp._val)\n            node._val = temp._val\n        }\n    }\n    updateHeight(node) // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.rb
### Delete node ###\ndef remove(val)\n  @root = remove_helper(@root, val)\nend\n\n### Recursively delete node (helper method) ###\ndef remove_helper(node, val)\n  return if node.nil?\n  # 1. Find node and delete\n  if val < node.val\n    node.left = remove_helper(node.left, val)\n  elsif val > node.val\n    node.right = remove_helper(node.right, val)\n  else\n    if node.left.nil? || node.right.nil?\n      child = node.left || node.right\n      # Number of child nodes = 0, delete node directly and return\n      return if child.nil?\n      # Number of child nodes = 1, delete node directly\n      node = child\n    else\n      # Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n      temp = node.right\n      while !temp.left.nil?\n        temp = temp.left\n      end\n      node.right = remove_helper(node.right, temp.val)\n      node.val = temp.val\n    end\n  end\n  # Update node height\n  update_height(node)\n  # 2. Perform rotation operation to restore balance to this subtree\n  rotate(node)\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#3-node-search","level":3,"title":"3.   Node Search","text":"

The node search operation in AVL trees is consistent with that in binary search trees, and will not be elaborated here.

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#754-typical-applications-of-avl-trees","level":2,"title":"7.5.4   Typical Applications of Avl Trees","text":"
  • Organizing and storing large-scale data, suitable for scenarios with high-frequency searches and low-frequency insertions and deletions.
  • Used to build index systems in databases.
  • Red-black trees are also a common type of balanced binary search tree. Compared to AVL trees, red-black trees have more relaxed balance conditions, require fewer rotation operations for node insertion and deletion, and have higher average efficiency for node addition and deletion operations.
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/binary_search_tree/","level":1,"title":"7.4   Binary Search Tree","text":"

As shown in Figure 7-16, a binary search tree satisfies the following conditions.

  1. For the root node, the value of all nodes in the left subtree \\(<\\) the value of the root node \\(<\\) the value of all nodes in the right subtree.
  2. The left and right subtrees of any node are also binary search trees, i.e., they satisfy condition 1. as well.

Figure 7-16   Binary search tree

","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#741-operations-on-a-binary-search-tree","level":2,"title":"7.4.1   Operations on a Binary Search Tree","text":"

We encapsulate the binary search tree as a class BinarySearchTree and declare a member variable root pointing to the tree's root node.

","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#1-searching-for-a-node","level":3,"title":"1.   Searching for a Node","text":"

Given a target node value num, we can search according to the properties of the binary search tree. As shown in Figure 7-17, we declare a node cur and start from the binary tree's root node root, looping to compare the node value cur.val with num.

  • If cur.val < num, it means the target node is in cur's right subtree, thus execute cur = cur.right.
  • If cur.val > num, it means the target node is in cur's left subtree, thus execute cur = cur.left.
  • If cur.val = num, it means the target node is found, exit the loop, and return the node.
<1><2><3><4>

Figure 7-17   Example of searching for a node in a binary search tree

The search operation in a binary search tree works on the same principle as the binary search algorithm, both eliminating half of the cases in each round. The number of loop iterations is at most the height of the binary tree. When the binary tree is balanced, it uses \\(O(\\log n)\\) time. The example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
def search(self, num: int) -> TreeNode | None:\n    \"\"\"Search node\"\"\"\n    cur = self._root\n    # Loop search, exit after passing leaf node\n    while cur is not None:\n        # Target node is in cur's right subtree\n        if cur.val < num:\n            cur = cur.right\n        # Target node is in cur's left subtree\n        elif cur.val > num:\n            cur = cur.left\n        # Found target node, exit loop\n        else:\n            break\n    return cur\n
binary_search_tree.cpp
/* Search node */\nTreeNode *search(int num) {\n    TreeNode *cur = root;\n    // Loop search, exit after passing leaf node\n    while (cur != nullptr) {\n        // Target node is in cur's right subtree\n        if (cur->val < num)\n            cur = cur->right;\n        // Target node is in cur's left subtree\n        else if (cur->val > num)\n            cur = cur->left;\n        // Found target node, exit loop\n        else\n            break;\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.java
/* Search node */\nTreeNode search(int num) {\n    TreeNode cur = root;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Target node is in cur's right subtree\n        if (cur.val < num)\n            cur = cur.right;\n        // Target node is in cur's left subtree\n        else if (cur.val > num)\n            cur = cur.left;\n        // Found target node, exit loop\n        else\n            break;\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.cs
/* Search node */\nTreeNode? Search(int num) {\n    TreeNode? cur = root;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Target node is in cur's right subtree\n        if (cur.val < num) cur =\n            cur.right;\n        // Target node is in cur's left subtree\n        else if (cur.val > num)\n            cur = cur.left;\n        // Found target node, exit loop\n        else\n            break;\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.go
/* Search node */\nfunc (bst *binarySearchTree) search(num int) *TreeNode {\n    node := bst.root\n    // Loop search, exit after passing leaf node\n    for node != nil {\n        if node.Val.(int) < num {\n            // Target node is in cur's right subtree\n            node = node.Right\n        } else if node.Val.(int) > num {\n            // Target node is in cur's left subtree\n            node = node.Left\n        } else {\n            // Found target node, exit loop\n            break\n        }\n    }\n    // Return target node\n    return node\n}\n
binary_search_tree.swift
/* Search node */\nfunc search(num: Int) -> TreeNode? {\n    var cur = root\n    // Loop search, exit after passing leaf node\n    while cur != nil {\n        // Target node is in cur's right subtree\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // Target node is in cur's left subtree\n        else if cur!.val > num {\n            cur = cur?.left\n        }\n        // Found target node, exit loop\n        else {\n            break\n        }\n    }\n    // Return target node\n    return cur\n}\n
binary_search_tree.js
/* Search node */\nsearch(num) {\n    let cur = this.root;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Target node is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Target node is in cur's left subtree\n        else if (cur.val > num) cur = cur.left;\n        // Found target node, exit loop\n        else break;\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.ts
/* Search node */\nsearch(num: number): TreeNode | null {\n    let cur = this.root;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Target node is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Target node is in cur's left subtree\n        else if (cur.val > num) cur = cur.left;\n        // Found target node, exit loop\n        else break;\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.dart
/* Search node */\nTreeNode? search(int _num) {\n  TreeNode? cur = _root;\n  // Loop search, exit after passing leaf node\n  while (cur != null) {\n    // Target node is in cur's right subtree\n    if (cur.val < _num)\n      cur = cur.right;\n    // Target node is in cur's left subtree\n    else if (cur.val > _num)\n      cur = cur.left;\n    // Found target node, exit loop\n    else\n      break;\n  }\n  // Return target node\n  return cur;\n}\n
binary_search_tree.rs
/* Search node */\npub fn search(&self, num: i32) -> OptionTreeNodeRc {\n    let mut cur = self.root.clone();\n    // Loop search, exit after passing leaf node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // Target node is in cur's right subtree\n            Ordering::Greater => cur = node.borrow().right.clone(),\n            // Target node is in cur's left subtree\n            Ordering::Less => cur = node.borrow().left.clone(),\n            // Found target node, exit loop\n            Ordering::Equal => break,\n        }\n    }\n\n    // Return target node\n    cur\n}\n
binary_search_tree.c
/* Search node */\nTreeNode *search(BinarySearchTree *bst, int num) {\n    TreeNode *cur = bst->root;\n    // Loop search, exit after passing leaf node\n    while (cur != NULL) {\n        if (cur->val < num) {\n            // Target node is in cur's right subtree\n            cur = cur->right;\n        } else if (cur->val > num) {\n            // Target node is in cur's left subtree\n            cur = cur->left;\n        } else {\n            // Found target node, exit loop\n            break;\n        }\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.kt
/* Search node */\nfun search(num: Int): TreeNode? {\n    var cur = root\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Target node is in cur's right subtree\n        cur = if (cur._val < num)\n            cur.right\n        // Target node is in cur's left subtree\n        else if (cur._val > num)\n            cur.left\n        // Found target node, exit loop\n        else\n            break\n    }\n    // Return target node\n    return cur\n}\n
binary_search_tree.rb
### Search node ###\ndef search(num)\n  cur = @root\n\n  # Loop search, exit after passing leaf node\n  while !cur.nil?\n    # Target node is in cur's right subtree\n    if cur.val < num\n      cur = cur.right\n    # Target node is in cur's left subtree\n    elsif cur.val > num\n      cur = cur.left\n    # Found target node, exit loop\n    else\n      break\n    end\n  end\n\n  cur\nend\n
","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#2-inserting-a-node","level":3,"title":"2.   Inserting a Node","text":"

Given an element num to be inserted, in order to maintain the property of the binary search tree \"left subtree < root node < right subtree,\" the insertion process is as shown in Figure 7-18.

  1. Finding the insertion position: Similar to the search operation, start from the root node and loop downward searching according to the size relationship between the current node value and num, until passing the leaf node (traversing to None) and then exit the loop.
  2. Insert the node at that position: Initialize node num and place it at the None position.

Figure 7-18   Inserting a node into a binary search tree

In the code implementation, note the following two points:

  • Binary search trees do not allow duplicate nodes; otherwise, it would violate its definition. Therefore, if the node to be inserted already exists in the tree, the insertion is not performed and it returns directly.
  • To implement the node insertion, we need to use node pre to save the node from the previous loop iteration. This way, when traversing to None, we can obtain its parent node, thereby completing the node insertion operation.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
def insert(self, num: int):\n    \"\"\"Insert node\"\"\"\n    # If tree is empty, initialize root node\n    if self._root is None:\n        self._root = TreeNode(num)\n        return\n    # Loop search, exit after passing leaf node\n    cur, pre = self._root, None\n    while cur is not None:\n        # Found duplicate node, return directly\n        if cur.val == num:\n            return\n        pre = cur\n        # Insertion position is in cur's right subtree\n        if cur.val < num:\n            cur = cur.right\n        # Insertion position is in cur's left subtree\n        else:\n            cur = cur.left\n    # Insert node\n    node = TreeNode(num)\n    if pre.val < num:\n        pre.right = node\n    else:\n        pre.left = node\n
binary_search_tree.cpp
/* Insert node */\nvoid insert(int num) {\n    // If tree is empty, initialize root node\n    if (root == nullptr) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode *cur = root, *pre = nullptr;\n    // Loop search, exit after passing leaf node\n    while (cur != nullptr) {\n        // Found duplicate node, return directly\n        if (cur->val == num)\n            return;\n        pre = cur;\n        // Insertion position is in cur's right subtree\n        if (cur->val < num)\n            cur = cur->right;\n        // Insertion position is in cur's left subtree\n        else\n            cur = cur->left;\n    }\n    // Insert node\n    TreeNode *node = new TreeNode(num);\n    if (pre->val < num)\n        pre->right = node;\n    else\n        pre->left = node;\n}\n
binary_search_tree.java
/* Insert node */\nvoid insert(int num) {\n    // If tree is empty, initialize root node\n    if (root == null) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode cur = root, pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found duplicate node, return directly\n        if (cur.val == num)\n            return;\n        pre = cur;\n        // Insertion position is in cur's right subtree\n        if (cur.val < num)\n            cur = cur.right;\n        // Insertion position is in cur's left subtree\n        else\n            cur = cur.left;\n    }\n    // Insert node\n    TreeNode node = new TreeNode(num);\n    if (pre.val < num)\n        pre.right = node;\n    else\n        pre.left = node;\n}\n
binary_search_tree.cs
/* Insert node */\nvoid Insert(int num) {\n    // If tree is empty, initialize root node\n    if (root == null) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode? cur = root, pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found duplicate node, return directly\n        if (cur.val == num)\n            return;\n        pre = cur;\n        // Insertion position is in cur's right subtree\n        if (cur.val < num)\n            cur = cur.right;\n        // Insertion position is in cur's left subtree\n        else\n            cur = cur.left;\n    }\n\n    // Insert node\n    TreeNode node = new(num);\n    if (pre != null) {\n        if (pre.val < num)\n            pre.right = node;\n        else\n            pre.left = node;\n    }\n}\n
binary_search_tree.go
/* Insert node */\nfunc (bst *binarySearchTree) insert(num int) {\n    cur := bst.root\n    // If tree is empty, initialize root node\n    if cur == nil {\n        bst.root = NewTreeNode(num)\n        return\n    }\n    // Node position before the node to be inserted\n    var pre *TreeNode = nil\n    // Loop search, exit after passing leaf node\n    for cur != nil {\n        if cur.Val == num {\n            return\n        }\n        pre = cur\n        if cur.Val.(int) < num {\n            cur = cur.Right\n        } else {\n            cur = cur.Left\n        }\n    }\n    // Insert node\n    node := NewTreeNode(num)\n    if pre.Val.(int) < num {\n        pre.Right = node\n    } else {\n        pre.Left = node\n    }\n}\n
binary_search_tree.swift
/* Insert node */\nfunc insert(num: Int) {\n    // If tree is empty, initialize root node\n    if root == nil {\n        root = TreeNode(x: num)\n        return\n    }\n    var cur = root\n    var pre: TreeNode?\n    // Loop search, exit after passing leaf node\n    while cur != nil {\n        // Found duplicate node, return directly\n        if cur!.val == num {\n            return\n        }\n        pre = cur\n        // Insertion position is in cur's right subtree\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // Insertion position is in cur's left subtree\n        else {\n            cur = cur?.left\n        }\n    }\n    // Insert node\n    let node = TreeNode(x: num)\n    if pre!.val < num {\n        pre?.right = node\n    } else {\n        pre?.left = node\n    }\n}\n
binary_search_tree.js
/* Insert node */\ninsert(num) {\n    // If tree is empty, initialize root node\n    if (this.root === null) {\n        this.root = new TreeNode(num);\n        return;\n    }\n    let cur = this.root,\n        pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Found duplicate node, return directly\n        if (cur.val === num) return;\n        pre = cur;\n        // Insertion position is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Insertion position is in cur's left subtree\n        else cur = cur.left;\n    }\n    // Insert node\n    const node = new TreeNode(num);\n    if (pre.val < num) pre.right = node;\n    else pre.left = node;\n}\n
binary_search_tree.ts
/* Insert node */\ninsert(num: number): void {\n    // If tree is empty, initialize root node\n    if (this.root === null) {\n        this.root = new TreeNode(num);\n        return;\n    }\n    let cur: TreeNode | null = this.root,\n        pre: TreeNode | null = null;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Found duplicate node, return directly\n        if (cur.val === num) return;\n        pre = cur;\n        // Insertion position is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Insertion position is in cur's left subtree\n        else cur = cur.left;\n    }\n    // Insert node\n    const node = new TreeNode(num);\n    if (pre!.val < num) pre!.right = node;\n    else pre!.left = node;\n}\n
binary_search_tree.dart
/* Insert node */\nvoid insert(int _num) {\n  // If tree is empty, initialize root node\n  if (_root == null) {\n    _root = TreeNode(_num);\n    return;\n  }\n  TreeNode? cur = _root;\n  TreeNode? pre = null;\n  // Loop search, exit after passing leaf node\n  while (cur != null) {\n    // Found duplicate node, return directly\n    if (cur.val == _num) return;\n    pre = cur;\n    // Insertion position is in cur's right subtree\n    if (cur.val < _num)\n      cur = cur.right;\n    // Insertion position is in cur's left subtree\n    else\n      cur = cur.left;\n  }\n  // Insert node\n  TreeNode? node = TreeNode(_num);\n  if (pre!.val < _num)\n    pre.right = node;\n  else\n    pre.left = node;\n}\n
binary_search_tree.rs
/* Insert node */\npub fn insert(&mut self, num: i32) {\n    // If tree is empty, initialize root node\n    if self.root.is_none() {\n        self.root = Some(TreeNode::new(num));\n        return;\n    }\n    let mut cur = self.root.clone();\n    let mut pre = None;\n    // Loop search, exit after passing leaf node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // Found duplicate node, return directly\n            Ordering::Equal => return,\n            // Insertion position is in cur's right subtree\n            Ordering::Greater => {\n                pre = cur.clone();\n                cur = node.borrow().right.clone();\n            }\n            // Insertion position is in cur's left subtree\n            Ordering::Less => {\n                pre = cur.clone();\n                cur = node.borrow().left.clone();\n            }\n        }\n    }\n    // Insert node\n    let pre = pre.unwrap();\n    let node = Some(TreeNode::new(num));\n    if num > pre.borrow().val {\n        pre.borrow_mut().right = node;\n    } else {\n        pre.borrow_mut().left = node;\n    }\n}\n
binary_search_tree.c
/* Insert node */\nvoid insert(BinarySearchTree *bst, int num) {\n    // If tree is empty, initialize root node\n    if (bst->root == NULL) {\n        bst->root = newTreeNode(num);\n        return;\n    }\n    TreeNode *cur = bst->root, *pre = NULL;\n    // Loop search, exit after passing leaf node\n    while (cur != NULL) {\n        // Found duplicate node, return directly\n        if (cur->val == num) {\n            return;\n        }\n        pre = cur;\n        if (cur->val < num) {\n            // Insertion position is in cur's right subtree\n            cur = cur->right;\n        } else {\n            // Insertion position is in cur's left subtree\n            cur = cur->left;\n        }\n    }\n    // Insert node\n    TreeNode *node = newTreeNode(num);\n    if (pre->val < num) {\n        pre->right = node;\n    } else {\n        pre->left = node;\n    }\n}\n
binary_search_tree.kt
/* Insert node */\nfun insert(num: Int) {\n    // If tree is empty, initialize root node\n    if (root == null) {\n        root = TreeNode(num)\n        return\n    }\n    var cur = root\n    var pre: TreeNode? = null\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found duplicate node, return directly\n        if (cur._val == num)\n            return\n        pre = cur\n        // Insertion position is in cur's right subtree\n        cur = if (cur._val < num)\n            cur.right\n        // Insertion position is in cur's left subtree\n        else\n            cur.left\n    }\n    // Insert node\n    val node = TreeNode(num)\n    if (pre?._val!! < num)\n        pre.right = node\n    else\n        pre.left = node\n}\n
binary_search_tree.rb
### Insert node ###\ndef insert(num)\n  # If tree is empty, initialize root node\n  if @root.nil?\n    @root = TreeNode.new(num)\n    return\n  end\n\n  # Loop search, exit after passing leaf node\n  cur, pre = @root, nil\n  while !cur.nil?\n    # Found duplicate node, return directly\n    return if cur.val == num\n\n    pre = cur\n    # Insertion position is in cur's right subtree\n    if cur.val < num\n      cur = cur.right\n    # Insertion position is in cur's left subtree\n    else\n      cur = cur.left\n    end\n  end\n\n  # Insert node\n  node = TreeNode.new(num)\n  if pre.val < num\n    pre.right = node\n  else\n    pre.left = node\n  end\nend\n

Similar to searching for a node, inserting a node uses \\(O(\\log n)\\) time.

","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#3-removing-a-node","level":3,"title":"3.   Removing a Node","text":"

First, find the target node in the binary tree, then remove it. Similar to node insertion, we need to ensure that after the removal operation is completed, the binary search tree's property of \"left subtree \\(<\\) root node \\(<\\) right subtree\" is still maintained. Therefore, depending on the number of child nodes the target node has, we divide it into 0, 1, and 2 three cases, and execute the corresponding node removal operations.

As shown in Figure 7-19, when the degree of the node to be removed is \\(0\\), it means the node is a leaf node and can be directly removed.

Figure 7-19   Removing a node in a binary search tree (degree 0)

As shown in Figure 7-20, when the degree of the node to be removed is \\(1\\), replacing the node to be removed with its child node is sufficient.

Figure 7-20   Removing a node in a binary search tree (degree 1)

When the degree of the node to be removed is \\(2\\), we cannot directly remove it; instead, we need to use a node to replace it. To maintain the binary search tree's property of \"left subtree \\(<\\) root node \\(<\\) right subtree,\" this node can be either the smallest node in the right subtree or the largest node in the left subtree.

Assuming we choose the smallest node in the right subtree (the next node in the inorder traversal), the removal process is as shown in Figure 7-21.

  1. Find the next node of the node to be removed in the \"inorder traversal sequence,\" denoted as tmp.
  2. Replace the value of the node to be removed with the value of tmp, and recursively remove node tmp in the tree.
<1><2><3><4>

Figure 7-21   Removing a node in a binary search tree (degree 2)

The node removal operation also uses \\(O(\\log n)\\) time, where finding the node to be removed requires \\(O(\\log n)\\) time, and obtaining the inorder successor node requires \\(O(\\log n)\\) time. Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
def remove(self, num: int):\n    \"\"\"Delete node\"\"\"\n    # If tree is empty, return directly\n    if self._root is None:\n        return\n    # Loop search, exit after passing leaf node\n    cur, pre = self._root, None\n    while cur is not None:\n        # Found node to delete, exit loop\n        if cur.val == num:\n            break\n        pre = cur\n        # Node to delete is in cur's right subtree\n        if cur.val < num:\n            cur = cur.right\n        # Node to delete is in cur's left subtree\n        else:\n            cur = cur.left\n    # If no node to delete, return directly\n    if cur is None:\n        return\n\n    # Number of child nodes = 0 or 1\n    if cur.left is None or cur.right is None:\n        # When number of child nodes = 0 / 1, child = null / that child node\n        child = cur.left or cur.right\n        # Delete node cur\n        if cur != self._root:\n            if pre.left == cur:\n                pre.left = child\n            else:\n                pre.right = child\n        else:\n            # If deleted node is root node, reassign root node\n            self._root = child\n    # Number of child nodes = 2\n    else:\n        # Get next node of cur in inorder traversal\n        tmp: TreeNode = cur.right\n        while tmp.left is not None:\n            tmp = tmp.left\n        # Recursively delete node tmp\n        self.remove(tmp.val)\n        # Replace cur with tmp\n        cur.val = tmp.val\n
binary_search_tree.cpp
/* Remove node */\nvoid remove(int num) {\n    // If tree is empty, return directly\n    if (root == nullptr)\n        return;\n    TreeNode *cur = root, *pre = nullptr;\n    // Loop search, exit after passing leaf node\n    while (cur != nullptr) {\n        // Found node to delete, exit loop\n        if (cur->val == num)\n            break;\n        pre = cur;\n        // Node to delete is in cur's right subtree\n        if (cur->val < num)\n            cur = cur->right;\n        // Node to delete is in cur's left subtree\n        else\n            cur = cur->left;\n    }\n    // If no node to delete, return directly\n    if (cur == nullptr)\n        return;\n    // Number of child nodes = 0 or 1\n    if (cur->left == nullptr || cur->right == nullptr) {\n        // When number of child nodes = 0 / 1, child = nullptr / that child node\n        TreeNode *child = cur->left != nullptr ? cur->left : cur->right;\n        // Delete node cur\n        if (cur != root) {\n            if (pre->left == cur)\n                pre->left = child;\n            else\n                pre->right = child;\n        } else {\n            // If deleted node is root node, reassign root node\n            root = child;\n        }\n        // Free memory\n        delete cur;\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        TreeNode *tmp = cur->right;\n        while (tmp->left != nullptr) {\n            tmp = tmp->left;\n        }\n        int tmpVal = tmp->val;\n        // Recursively delete node tmp\n        remove(tmp->val);\n        // Replace cur with tmp\n        cur->val = tmpVal;\n    }\n}\n
binary_search_tree.java
/* Remove node */\nvoid remove(int num) {\n    // If tree is empty, return directly\n    if (root == null)\n        return;\n    TreeNode cur = root, pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found node to delete, exit loop\n        if (cur.val == num)\n            break;\n        pre = cur;\n        // Node to delete is in cur's right subtree\n        if (cur.val < num)\n            cur = cur.right;\n        // Node to delete is in cur's left subtree\n        else\n            cur = cur.left;\n    }\n    // If no node to delete, return directly\n    if (cur == null)\n        return;\n    // Number of child nodes = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        TreeNode child = cur.left != null ? cur.left : cur.right;\n        // Delete node cur\n        if (cur != root) {\n            if (pre.left == cur)\n                pre.left = child;\n            else\n                pre.right = child;\n        } else {\n            // If deleted node is root node, reassign root node\n            root = child;\n        }\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        TreeNode tmp = cur.right;\n        while (tmp.left != null) {\n            tmp = tmp.left;\n        }\n        // Recursively delete node tmp\n        remove(tmp.val);\n        // Replace cur with tmp\n        cur.val = tmp.val;\n    }\n}\n
binary_search_tree.cs
/* Remove node */\nvoid Remove(int num) {\n    // If tree is empty, return directly\n    if (root == null)\n        return;\n    TreeNode? cur = root, pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found node to delete, exit loop\n        if (cur.val == num)\n            break;\n        pre = cur;\n        // Node to delete is in cur's right subtree\n        if (cur.val < num)\n            cur = cur.right;\n        // Node to delete is in cur's left subtree\n        else\n            cur = cur.left;\n    }\n    // If no node to delete, return directly\n    if (cur == null)\n        return;\n    // Number of child nodes = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        TreeNode? child = cur.left ?? cur.right;\n        // Delete node cur\n        if (cur != root) {\n            if (pre!.left == cur)\n                pre.left = child;\n            else\n                pre.right = child;\n        } else {\n            // If deleted node is root node, reassign root node\n            root = child;\n        }\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        TreeNode? tmp = cur.right;\n        while (tmp.left != null) {\n            tmp = tmp.left;\n        }\n        // Recursively delete node tmp\n        Remove(tmp.val!.Value);\n        // Replace cur with tmp\n        cur.val = tmp.val;\n    }\n}\n
binary_search_tree.go
/* Remove node */\nfunc (bst *binarySearchTree) remove(num int) {\n    cur := bst.root\n    // If tree is empty, return directly\n    if cur == nil {\n        return\n    }\n    // Node position before the node to be removed\n    var pre *TreeNode = nil\n    // Loop search, exit after passing leaf node\n    for cur != nil {\n        if cur.Val == num {\n            break\n        }\n        pre = cur\n        if cur.Val.(int) < num {\n            // Node to be removed is in right subtree\n            cur = cur.Right\n        } else {\n            // Node to be removed is in left subtree\n            cur = cur.Left\n        }\n    }\n    // If no node to delete, return directly\n    if cur == nil {\n        return\n    }\n    // Number of child nodes is 0 or 1\n    if cur.Left == nil || cur.Right == nil {\n        var child *TreeNode = nil\n        // Get child node of node to be removed\n        if cur.Left != nil {\n            child = cur.Left\n        } else {\n            child = cur.Right\n        }\n        // Delete node cur\n        if cur != bst.root {\n            if pre.Left == cur {\n                pre.Left = child\n            } else {\n                pre.Right = child\n            }\n        } else {\n            // If deleted node is root node, reassign root node\n            bst.root = child\n        }\n        // Number of child nodes is 2\n    } else {\n        // Get next node of node cur to be removed in in-order traversal\n        tmp := cur.Right\n        for tmp.Left != nil {\n            tmp = tmp.Left\n        }\n        // Recursively delete node tmp\n        bst.remove(tmp.Val.(int))\n        // Replace cur with tmp\n        cur.Val = tmp.Val\n    }\n}\n
binary_search_tree.swift
/* Remove node */\nfunc remove(num: Int) {\n    // If tree is empty, return directly\n    if root == nil {\n        return\n    }\n    var cur = root\n    var pre: TreeNode?\n    // Loop search, exit after passing leaf node\n    while cur != nil {\n        // Found node to delete, exit loop\n        if cur!.val == num {\n            break\n        }\n        pre = cur\n        // Node to delete is in cur's right subtree\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // Node to delete is in cur's left subtree\n        else {\n            cur = cur?.left\n        }\n    }\n    // If no node to delete, return directly\n    if cur == nil {\n        return\n    }\n    // Number of child nodes = 0 or 1\n    if cur?.left == nil || cur?.right == nil {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        let child = cur?.left ?? cur?.right\n        // Delete node cur\n        if cur !== root {\n            if pre?.left === cur {\n                pre?.left = child\n            } else {\n                pre?.right = child\n            }\n        } else {\n            // If deleted node is root node, reassign root node\n            root = child\n        }\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        var tmp = cur?.right\n        while tmp?.left != nil {\n            tmp = tmp?.left\n        }\n        // Recursively delete node tmp\n        remove(num: tmp!.val)\n        // Replace cur with tmp\n        cur?.val = tmp!.val\n    }\n}\n
binary_search_tree.js
/* Remove node */\nremove(num) {\n    // If tree is empty, return directly\n    if (this.root === null) return;\n    let cur = this.root,\n        pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Found node to delete, exit loop\n        if (cur.val === num) break;\n        pre = cur;\n        // Node to delete is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Node to delete is in cur's left subtree\n        else cur = cur.left;\n    }\n    // If no node to delete, return directly\n    if (cur === null) return;\n    // Number of child nodes = 0 or 1\n    if (cur.left === null || cur.right === null) {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        const child = cur.left !== null ? cur.left : cur.right;\n        // Delete node cur\n        if (cur !== this.root) {\n            if (pre.left === cur) pre.left = child;\n            else pre.right = child;\n        } else {\n            // If deleted node is root node, reassign root node\n            this.root = child;\n        }\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        let tmp = cur.right;\n        while (tmp.left !== null) {\n            tmp = tmp.left;\n        }\n        // Recursively delete node tmp\n        this.remove(tmp.val);\n        // Replace cur with tmp\n        cur.val = tmp.val;\n    }\n}\n
binary_search_tree.ts
/* Remove node */\nremove(num: number): void {\n    // If tree is empty, return directly\n    if (this.root === null) return;\n    let cur: TreeNode | null = this.root,\n        pre: TreeNode | null = null;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Found node to delete, exit loop\n        if (cur.val === num) break;\n        pre = cur;\n        // Node to delete is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Node to delete is in cur's left subtree\n        else cur = cur.left;\n    }\n    // If no node to delete, return directly\n    if (cur === null) return;\n    // Number of child nodes = 0 or 1\n    if (cur.left === null || cur.right === null) {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        const child: TreeNode | null =\n            cur.left !== null ? cur.left : cur.right;\n        // Delete node cur\n        if (cur !== this.root) {\n            if (pre!.left === cur) pre!.left = child;\n            else pre!.right = child;\n        } else {\n            // If deleted node is root node, reassign root node\n            this.root = child;\n        }\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        let tmp: TreeNode | null = cur.right;\n        while (tmp!.left !== null) {\n            tmp = tmp!.left;\n        }\n        // Recursively delete node tmp\n        this.remove(tmp!.val);\n        // Replace cur with tmp\n        cur.val = tmp!.val;\n    }\n}\n
binary_search_tree.dart
/* Remove node */\nvoid remove(int _num) {\n  // If tree is empty, return directly\n  if (_root == null) return;\n  TreeNode? cur = _root;\n  TreeNode? pre = null;\n  // Loop search, exit after passing leaf node\n  while (cur != null) {\n    // Found node to delete, exit loop\n    if (cur.val == _num) break;\n    pre = cur;\n    // Node to delete is in cur's right subtree\n    if (cur.val < _num)\n      cur = cur.right;\n    // Node to delete is in cur's left subtree\n    else\n      cur = cur.left;\n  }\n  // If no node to delete, return directly\n  if (cur == null) return;\n  // Number of child nodes = 0 or 1\n  if (cur.left == null || cur.right == null) {\n    // When number of child nodes = 0 / 1, child = null / that child node\n    TreeNode? child = cur.left ?? cur.right;\n    // Delete node cur\n    if (cur != _root) {\n      if (pre!.left == cur)\n        pre.left = child;\n      else\n        pre.right = child;\n    } else {\n      // If deleted node is root node, reassign root node\n      _root = child;\n    }\n  } else {\n    // Number of child nodes = 2\n    // Get next node of cur in inorder traversal\n    TreeNode? tmp = cur.right;\n    while (tmp!.left != null) {\n      tmp = tmp.left;\n    }\n    // Recursively delete node tmp\n    remove(tmp.val);\n    // Replace cur with tmp\n    cur.val = tmp.val;\n  }\n}\n
binary_search_tree.rs
/* Remove node */\npub fn remove(&mut self, num: i32) {\n    // If tree is empty, return directly\n    if self.root.is_none() {\n        return;\n    }\n    let mut cur = self.root.clone();\n    let mut pre = None;\n    // Loop search, exit after passing leaf node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // Found node to delete, exit loop\n            Ordering::Equal => break,\n            // Node to delete is in cur's right subtree\n            Ordering::Greater => {\n                pre = cur.clone();\n                cur = node.borrow().right.clone();\n            }\n            // Node to delete is in cur's left subtree\n            Ordering::Less => {\n                pre = cur.clone();\n                cur = node.borrow().left.clone();\n            }\n        }\n    }\n    // If no node to delete, return directly\n    if cur.is_none() {\n        return;\n    }\n    let cur = cur.unwrap();\n    let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone());\n    match (left_child.clone(), right_child.clone()) {\n        // Number of child nodes = 0 or 1\n        (None, None) | (Some(_), None) | (None, Some(_)) => {\n            // When number of child nodes = 0 / 1, child = nullptr / that child node\n            let child = left_child.or(right_child);\n            let pre = pre.unwrap();\n            // Delete node cur\n            if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) {\n                let left = pre.borrow().left.clone();\n                if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) {\n                    pre.borrow_mut().left = child;\n                } else {\n                    pre.borrow_mut().right = child;\n                }\n            } else {\n                // If deleted node is root node, reassign root node\n                self.root = child;\n            }\n        }\n        // Number of child nodes = 2\n        (Some(_), Some(_)) => {\n            // Get next node of cur in inorder traversal\n            let mut tmp = cur.borrow().right.clone();\n            while let Some(node) = tmp.clone() {\n                if node.borrow().left.is_some() {\n                    tmp = node.borrow().left.clone();\n                } else {\n                    break;\n                }\n            }\n            let tmp_val = tmp.unwrap().borrow().val;\n            // Recursively delete node tmp\n            self.remove(tmp_val);\n            // Replace cur with tmp\n            cur.borrow_mut().val = tmp_val;\n        }\n    }\n}\n
binary_search_tree.c
/* Remove node */\n// Cannot use remove keyword here due to stdio.h inclusion\nvoid removeItem(BinarySearchTree *bst, int num) {\n    // If tree is empty, return directly\n    if (bst->root == NULL)\n        return;\n    TreeNode *cur = bst->root, *pre = NULL;\n    // Loop search, exit after passing leaf node\n    while (cur != NULL) {\n        // Found node to delete, exit loop\n        if (cur->val == num)\n            break;\n        pre = cur;\n        if (cur->val < num) {\n            // Node to delete is in right subtree of root\n            cur = cur->right;\n        } else {\n            // Node to delete is in left subtree of root\n            cur = cur->left;\n        }\n    }\n    // If no node to delete, return directly\n    if (cur == NULL)\n        return;\n    // Check if node to delete has children\n    if (cur->left == NULL || cur->right == NULL) {\n        /* Number of child nodes = 0 or 1 */\n        // When number of child nodes = 0 / 1, child = nullptr / that child node\n        TreeNode *child = cur->left != NULL ? cur->left : cur->right;\n        // Delete node cur\n        if (pre->left == cur) {\n            pre->left = child;\n        } else {\n            pre->right = child;\n        }\n        // Free memory\n        free(cur);\n    } else {\n        /* Number of child nodes = 2 */\n        // Get next node of cur in inorder traversal\n        TreeNode *tmp = cur->right;\n        while (tmp->left != NULL) {\n            tmp = tmp->left;\n        }\n        int tmpVal = tmp->val;\n        // Recursively delete node tmp\n        removeItem(bst, tmp->val);\n        // Replace cur with tmp\n        cur->val = tmpVal;\n    }\n}\n
binary_search_tree.kt
/* Remove node */\nfun remove(num: Int) {\n    // If tree is empty, return directly\n    if (root == null)\n        return\n    var cur = root\n    var pre: TreeNode? = null\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found node to delete, exit loop\n        if (cur._val == num)\n            break\n        pre = cur\n        // Node to delete is in cur's right subtree\n        cur = if (cur._val < num)\n            cur.right\n        // Node to delete is in cur's left subtree\n        else\n            cur.left\n    }\n    // If no node to delete, return directly\n    if (cur == null)\n        return\n    // Number of child nodes = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        val child = if (cur.left != null)\n            cur.left\n        else\n            cur.right\n        // Delete node cur\n        if (cur != root) {\n            if (pre!!.left == cur)\n                pre.left = child\n            else\n                pre.right = child\n        } else {\n            // If deleted node is root node, reassign root node\n            root = child\n        }\n        // Number of child nodes = 2\n    } else {\n        // Get next node of cur in inorder traversal\n        var tmp = cur.right\n        while (tmp!!.left != null) {\n            tmp = tmp.left\n        }\n        // Recursively delete node tmp\n        remove(tmp._val)\n        // Replace cur with tmp\n        cur._val = tmp._val\n    }\n}\n
binary_search_tree.rb
### Delete node ###\ndef remove(num)\n  # If tree is empty, return directly\n  return if @root.nil?\n\n  # Loop search, exit after passing leaf node\n  cur, pre = @root, nil\n  while !cur.nil?\n    # Found node to delete, exit loop\n    break if cur.val == num\n\n    pre = cur\n    # Node to delete is in cur's right subtree\n    if cur.val < num\n      cur = cur.right\n    # Node to delete is in cur's left subtree\n    else\n      cur = cur.left\n    end\n  end\n  # If no node to delete, return directly\n  return if cur.nil?\n\n  # Number of child nodes = 0 or 1\n  if cur.left.nil? || cur.right.nil?\n    # When number of child nodes = 0 / 1, child = null / that child node\n    child = cur.left || cur.right\n    # Delete node cur\n    if cur != @root\n      if pre.left == cur\n        pre.left = child\n      else\n        pre.right = child\n      end\n    else\n      # If deleted node is root node, reassign root node\n      @root = child\n    end\n  # Number of child nodes = 2\n  else\n    # Get next node of cur in inorder traversal\n    tmp = cur.right\n    while !tmp.left.nil?\n      tmp = tmp.left\n    end\n    # Recursively delete node tmp\n    remove(tmp.val)\n    # Replace cur with tmp\n    cur.val = tmp.val\n  end\nend\n
","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#4-inorder-traversal-is-ordered","level":3,"title":"4.   Inorder Traversal Is Ordered","text":"

As shown in Figure 7-22, the inorder traversal of a binary tree follows the \"left \\(\\rightarrow\\) root \\(\\rightarrow\\) right\" traversal order, while the binary search tree satisfies the \"left child node \\(<\\) root node \\(<\\) right child node\" size relationship.

This means that when performing an inorder traversal in a binary search tree, the next smallest node is always traversed first, thus yielding an important property: The inorder traversal sequence of a binary search tree is ascending.

Using the property of inorder traversal being ascending, we can obtain ordered data in a binary search tree in only \\(O(n)\\) time, without the need for additional sorting operations, which is very efficient.

Figure 7-22   Inorder traversal sequence of a binary search tree

","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#742-efficiency-of-binary-search-trees","level":2,"title":"7.4.2   Efficiency of Binary Search Trees","text":"

Given a set of data, we consider using an array or a binary search tree for storage. Observing Table 7-2, all operations in a binary search tree have logarithmic time complexity, providing stable and efficient performance. Arrays are more efficient than binary search trees only in scenarios with high-frequency additions and low-frequency searches and deletions.

Table 7-2   Efficiency comparison between arrays and search trees

Unsorted array Binary search tree Search element \\(O(n)\\) \\(O(\\log n)\\) Insert element \\(O(1)\\) \\(O(\\log n)\\) Remove element \\(O(n)\\) \\(O(\\log n)\\)

In the ideal case, a binary search tree is \"balanced,\" such that any node can be found within \\(\\log n\\) loop iterations.

However, if we continuously insert and remove nodes in a binary search tree, it may degenerate into a linked list as shown in Figure 7-23, where the time complexity of various operations also degrades to \\(O(n)\\).

Figure 7-23   Degradation of a binary search tree

","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#743-common-applications-of-binary-search-trees","level":2,"title":"7.4.3   Common Applications of Binary Search Trees","text":"
  • Used as multi-level indexes in systems to implement efficient search, insertion, and removal operations.
  • Serves as the underlying data structure for certain search algorithms.
  • Used to store data streams to maintain their ordered state.
","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/","level":1,"title":"7.1   Binary Tree","text":"

A binary tree is a non-linear data structure that represents the derivation relationship between \"ancestors\" and \"descendants\" and embodies the divide-and-conquer logic of \"one divides into two\". Similar to a linked list, the basic unit of a binary tree is a node, and each node contains a value, a reference to its left child node, and a reference to its right child node.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
class TreeNode:\n    \"\"\"Binary tree node\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                # Node value\n        self.left: TreeNode | None = None  # Reference to left child node\n        self.right: TreeNode | None = None # Reference to right child node\n
/* Binary tree node */\nstruct TreeNode {\n    int val;          // Node value\n    TreeNode *left;   // Pointer to left child node\n    TreeNode *right;  // Pointer to right child node\n    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}\n};\n
/* Binary tree node */\nclass TreeNode {\n    int val;         // Node value\n    TreeNode left;   // Reference to left child node\n    TreeNode right;  // Reference to right child node\n    TreeNode(int x) { val = x; }\n}\n
/* Binary tree node */\nclass TreeNode(int? x) {\n    public int? val = x;    // Node value\n    public TreeNode? left;  // Reference to left child node\n    public TreeNode? right; // Reference to right child node\n}\n
/* Binary tree node */\ntype TreeNode struct {\n    Val   int\n    Left  *TreeNode\n    Right *TreeNode\n}\n/* Constructor */\nfunc NewTreeNode(v int) *TreeNode {\n    return &TreeNode{\n        Left:  nil, // Pointer to left child node\n        Right: nil, // Pointer to right child node\n        Val:   v,   // Node value\n    }\n}\n
/* Binary tree node */\nclass TreeNode {\n    var val: Int // Node value\n    var left: TreeNode? // Reference to left child node\n    var right: TreeNode? // Reference to right child node\n\n    init(x: Int) {\n        val = x\n    }\n}\n
/* Binary tree node */\nclass TreeNode {\n    val; // Node value\n    left; // Pointer to left child node\n    right; // Pointer to right child node\n    constructor(val, left, right) {\n        this.val = val === undefined ? 0 : val;\n        this.left = left === undefined ? null : left;\n        this.right = right === undefined ? null : right;\n    }\n}\n
/* Binary tree node */\nclass TreeNode {\n    val: number;\n    left: TreeNode | null;\n    right: TreeNode | null;\n\n    constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.left = left === undefined ? null : left; // Reference to left child node\n        this.right = right === undefined ? null : right; // Reference to right child node\n    }\n}\n
/* Binary tree node */\nclass TreeNode {\n  int val;         // Node value\n  TreeNode? left;  // Reference to left child node\n  TreeNode? right; // Reference to right child node\n  TreeNode(this.val, [this.left, this.right]);\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Binary tree node */\nstruct TreeNode {\n    val: i32,                               // Node value\n    left: Option<Rc<RefCell<TreeNode>>>,    // Reference to left child node\n    right: Option<Rc<RefCell<TreeNode>>>,   // Reference to right child node\n}\n\nimpl TreeNode {\n    /* Constructor */\n    fn new(val: i32) -> Rc<RefCell<Self>> {\n        Rc::new(RefCell::new(Self {\n            val,\n            left: None,\n            right: None\n        }))\n    }\n}\n
/* Binary tree node */\ntypedef struct TreeNode {\n    int val;                // Node value\n    int height;             // Node height\n    struct TreeNode *left;  // Pointer to left child node\n    struct TreeNode *right; // Pointer to right child node\n} TreeNode;\n\n/* Constructor */\nTreeNode *newTreeNode(int val) {\n    TreeNode *node;\n\n    node = (TreeNode *)malloc(sizeof(TreeNode));\n    node->val = val;\n    node->height = 0;\n    node->left = NULL;\n    node->right = NULL;\n    return node;\n}\n
/* Binary tree node */\nclass TreeNode(val _val: Int) {  // Node value\n    val left: TreeNode? = null   // Reference to left child node\n    val right: TreeNode? = null  // Reference to right child node\n}\n
### Binary tree node class ###\nclass TreeNode\n  attr_accessor :val    # Node value\n  attr_accessor :left   # Reference to left child node\n  attr_accessor :right  # Reference to right child node\n\n  def initialize(val)\n    @val = val\n  end\nend\n

Each node has two references (pointers), pointing respectively to the left-child node and right-child node. This node is called the parent node of these two child nodes. When given a node of a binary tree, we call the tree formed by this node's left child and all nodes below it the left subtree of this node. Similarly, the right subtree can be defined.

In a binary tree, except leaf nodes, all other nodes contain child nodes and non-empty subtrees. As shown in Figure 7-1, if \"Node 2\" is regarded as a parent node, its left and right child nodes are \"Node 4\" and \"Node 5\" respectively. The left subtree is formed by \"Node 4\" and all nodes beneath it, while the right subtree is formed by \"Node 5\" and all nodes beneath it.

Figure 7-1   Parent Node, child Node, subtree

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#711-common-terminology-of-binary-trees","level":2,"title":"7.1.1   Common Terminology of Binary Trees","text":"

The commonly used terminology of binary trees is shown in Figure 7-2.

  • Root node: The node at the top level of a binary tree, which does not have a parent node.
  • Leaf node: A node that does not have any child nodes, with both of its pointers pointing to None.
  • Edge: A line segment that connects two nodes, representing a reference (pointer) between the nodes.
  • The level of a node: It increases from top to bottom, with the root node being at level 1.
  • The degree of a node: The number of child nodes that a node has. In a binary tree, the degree can be 0, 1, or 2.
  • The height of a binary tree: The number of edges from the root node to the farthest leaf node.
  • The depth of a node: The number of edges from the root node to the node.
  • The height of a node: The number of edges from the farthest leaf node to the node.

Figure 7-2   Common Terminology of Binary Trees

Tip

Please note that we usually define \"height\" and \"depth\" as \"the number of edges traversed\", but some questions or textbooks may define them as \"the number of nodes traversed\". In this case, both height and depth need to be incremented by 1.

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#712-basic-operations-of-binary-trees","level":2,"title":"7.1.2   Basic Operations of Binary Trees","text":"","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#1-initializing-a-binary-tree","level":3,"title":"1.   Initializing a Binary Tree","text":"

Similar to a linked list, the initialization of a binary tree involves first creating the nodes and then establishing the references (pointers) between them.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree.py
# Initializing a binary tree\n# Initializing nodes\nn1 = TreeNode(val=1)\nn2 = TreeNode(val=2)\nn3 = TreeNode(val=3)\nn4 = TreeNode(val=4)\nn5 = TreeNode(val=5)\n# Linking references (pointers) between nodes\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
binary_tree.cpp
/* Initializing a binary tree */\n// Initializing nodes\nTreeNode* n1 = new TreeNode(1);\nTreeNode* n2 = new TreeNode(2);\nTreeNode* n3 = new TreeNode(3);\nTreeNode* n4 = new TreeNode(4);\nTreeNode* n5 = new TreeNode(5);\n// Linking references (pointers) between nodes\nn1->left = n2;\nn1->right = n3;\nn2->left = n4;\nn2->right = n5;\n
binary_tree.java
// Initializing nodes\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// Linking references (pointers) between nodes\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.cs
/* Initializing a binary tree */\n// Initializing nodes\nTreeNode n1 = new(1);\nTreeNode n2 = new(2);\nTreeNode n3 = new(3);\nTreeNode n4 = new(4);\nTreeNode n5 = new(5);\n// Linking references (pointers) between nodes\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.go
/* Initializing a binary tree */\n// Initializing nodes\nn1 := NewTreeNode(1)\nn2 := NewTreeNode(2)\nn3 := NewTreeNode(3)\nn4 := NewTreeNode(4)\nn5 := NewTreeNode(5)\n// Linking references (pointers) between nodes\nn1.Left = n2\nn1.Right = n3\nn2.Left = n4\nn2.Right = n5\n
binary_tree.swift
// Initializing nodes\nlet n1 = TreeNode(x: 1)\nlet n2 = TreeNode(x: 2)\nlet n3 = TreeNode(x: 3)\nlet n4 = TreeNode(x: 4)\nlet n5 = TreeNode(x: 5)\n// Linking references (pointers) between nodes\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
binary_tree.js
/* Initializing a binary tree */\n// Initializing nodes\nlet n1 = new TreeNode(1),\n    n2 = new TreeNode(2),\n    n3 = new TreeNode(3),\n    n4 = new TreeNode(4),\n    n5 = new TreeNode(5);\n// Linking references (pointers) between nodes\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.ts
/* Initializing a binary tree */\n// Initializing nodes\nlet n1 = new TreeNode(1),\n    n2 = new TreeNode(2),\n    n3 = new TreeNode(3),\n    n4 = new TreeNode(4),\n    n5 = new TreeNode(5);\n// Linking references (pointers) between nodes\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.dart
/* Initializing a binary tree */\n// Initializing nodes\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// Linking references (pointers) between nodes\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.rs
// Initializing nodes\nlet n1 = TreeNode::new(1);\nlet n2 = TreeNode::new(2);\nlet n3 = TreeNode::new(3);\nlet n4 = TreeNode::new(4);\nlet n5 = TreeNode::new(5);\n// Linking references (pointers) between nodes\nn1.borrow_mut().left = Some(n2.clone());\nn1.borrow_mut().right = Some(n3);\nn2.borrow_mut().left = Some(n4);\nn2.borrow_mut().right = Some(n5);\n
binary_tree.c
/* Initializing a binary tree */\n// Initializing nodes\nTreeNode *n1 = newTreeNode(1);\nTreeNode *n2 = newTreeNode(2);\nTreeNode *n3 = newTreeNode(3);\nTreeNode *n4 = newTreeNode(4);\nTreeNode *n5 = newTreeNode(5);\n// Linking references (pointers) between nodes\nn1->left = n2;\nn1->right = n3;\nn2->left = n4;\nn2->right = n5;\n
binary_tree.kt
// Initializing nodes\nval n1 = TreeNode(1)\nval n2 = TreeNode(2)\nval n3 = TreeNode(3)\nval n4 = TreeNode(4)\nval n5 = TreeNode(5)\n// Linking references (pointers) between nodes\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
binary_tree.rb
# Initializing a binary tree\n# Initializing nodes\nn1 = TreeNode.new(1)\nn2 = TreeNode.new(2)\nn3 = TreeNode.new(3)\nn4 = TreeNode.new(4)\nn5 = TreeNode.new(5)\n# Linking references (pointers) between nodes\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
Code Visualization

Full Screen >

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#2-inserting-and-removing-nodes","level":3,"title":"2.   Inserting and Removing Nodes","text":"

Similar to a linked list, inserting and removing nodes in a binary tree can be achieved by modifying pointers. Figure 7-3 provides an example.

Figure 7-3   Inserting and removing nodes in a binary tree

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree.py
# Inserting and removing nodes\np = TreeNode(0)\n# Inserting node P between n1 -> n2\nn1.left = p\np.left = n2\n# Removing node P\nn1.left = n2\n
binary_tree.cpp
/* Inserting and removing nodes */\nTreeNode* P = new TreeNode(0);\n// Inserting node P between n1 and n2\nn1->left = P;\nP->left = n2;\n// Removing node P\nn1->left = n2;\n
binary_tree.java
TreeNode P = new TreeNode(0);\n// Inserting node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Removing node P\nn1.left = n2;\n
binary_tree.cs
/* Inserting and removing nodes */\nTreeNode P = new(0);\n// Inserting node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Removing node P\nn1.left = n2;\n
binary_tree.go
/* Inserting and removing nodes */\n// Inserting node P between n1 and n2\np := NewTreeNode(0)\nn1.Left = p\np.Left = n2\n// Removing node P\nn1.Left = n2\n
binary_tree.swift
let P = TreeNode(x: 0)\n// Inserting node P between n1 and n2\nn1.left = P\nP.left = n2\n// Removing node P\nn1.left = n2\n
binary_tree.js
/* Inserting and removing nodes */\nlet P = new TreeNode(0);\n// Inserting node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Removing node P\nn1.left = n2;\n
binary_tree.ts
/* Inserting and removing nodes */\nconst P = new TreeNode(0);\n// Inserting node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Removing node P\nn1.left = n2;\n
binary_tree.dart
/* Inserting and removing nodes */\nTreeNode P = new TreeNode(0);\n// Inserting node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Removing node P\nn1.left = n2;\n
binary_tree.rs
let p = TreeNode::new(0);\n// Inserting node P between n1 and n2\nn1.borrow_mut().left = Some(p.clone());\np.borrow_mut().left = Some(n2.clone());\n// Removing node P\nn1.borrow_mut().left = Some(n2);\n
binary_tree.c
/* Inserting and removing nodes */\nTreeNode *P = newTreeNode(0);\n// Inserting node P between n1 and n2\nn1->left = P;\nP->left = n2;\n// Removing node P\nn1->left = n2;\n
binary_tree.kt
val P = TreeNode(0)\n// Inserting node P between n1 and n2\nn1.left = P\nP.left = n2\n// Removing node P\nn1.left = n2\n
binary_tree.rb
# Inserting and removing nodes\n_p = TreeNode.new(0)\n# Inserting node _p between n1 and n2\nn1.left = _p\n_p.left = n2\n# Removing node _p\nn1.left = n2\n
Code Visualization

Full Screen >

Tip

It should be noted that inserting nodes may change the original logical structure of the binary tree, while removing nodes typically involves removing the node and all its subtrees. Therefore, in a binary tree, insertion and removal are usually performed through a set of operations to achieve meaningful outcomes.

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#713-common-types-of-binary-trees","level":2,"title":"7.1.3   Common Types of Binary Trees","text":"","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#1-perfect-binary-tree","level":3,"title":"1.   Perfect Binary Tree","text":"

As shown in Figure 7-4, a perfect binary tree has all levels completely filled with nodes. In a perfect binary tree, leaf nodes have a degree of \\(0\\), while all other nodes have a degree of \\(2\\). If the tree height is \\(h\\), the total number of nodes is \\(2^{h+1} - 1\\), exhibiting a standard exponential relationship that reflects the common phenomenon of cell division in nature.

Tip

Please note that in the Chinese community, a perfect binary tree is often referred to as a full binary tree.

Figure 7-4   Perfect binary tree

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#2-complete-binary-tree","level":3,"title":"2.   Complete Binary Tree","text":"

As shown in Figure 7-5, a complete binary tree only allows the bottom level to be incompletely filled, and the nodes at the bottom level must be filled continuously from left to right. Note that a perfect binary tree is also a complete binary tree.

Figure 7-5   Complete binary tree

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#3-full-binary-tree","level":3,"title":"3.   Full Binary Tree","text":"

As shown in Figure 7-6, in a full binary tree, all nodes except leaf nodes have two child nodes.

Figure 7-6   Full binary tree

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#4-balanced-binary-tree","level":3,"title":"4.   Balanced Binary Tree","text":"

As shown in Figure 7-7, in a balanced binary tree, the absolute difference between the height of the left and right subtrees of any node does not exceed 1.

Figure 7-7   Balanced binary tree

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#714-degeneration-of-binary-trees","level":2,"title":"7.1.4   Degeneration of Binary Trees","text":"

Figure 7-8 shows the ideal and degenerate structures of binary trees. When every level of a binary tree is filled, it reaches the \"perfect binary tree\" state; when all nodes are biased toward one side, the binary tree degenerates into a \"linked list\".

  • A perfect binary tree is the ideal case, fully leveraging the \"divide and conquer\" advantage of binary trees.
  • A linked list represents the other extreme, where all operations become linear operations with time complexity degrading to \\(O(n)\\).

Figure 7-8   The Best and Worst Structures of Binary Trees

As shown in Table 7-1, in the best and worst structures, the binary tree achieves either maximum or minimum values for leaf node count, total number of nodes, and height.

Table 7-1   The Best and Worst Structures of Binary Trees

Perfect binary tree Linked list Number of nodes at level \\(i\\) \\(2^{i-1}\\) \\(1\\) Number of leaf nodes in a tree with height \\(h\\) \\(2^h\\) \\(1\\) Total number of nodes in a tree with height \\(h\\) \\(2^{h+1} - 1\\) \\(h + 1\\) Height of a tree with \\(n\\) total nodes \\(\\log_2 (n+1) - 1\\) \\(n - 1\\)","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/","level":1,"title":"7.2   Binary Tree Traversal","text":"

From a physical structure perspective, a tree is a data structure based on linked lists. Hence, its traversal method involves accessing nodes one by one through pointers. However, a tree is a non-linear data structure, which makes traversing a tree more complex than traversing a linked list, requiring the assistance of search algorithms.

The common traversal methods for binary trees include level-order traversal, pre-order traversal, in-order traversal, and post-order traversal.

","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#721-level-order-traversal","level":2,"title":"7.2.1   Level-Order Traversal","text":"

As shown in Figure 7-9, level-order traversal traverses the binary tree from top to bottom, layer by layer. Within each level, it visits nodes from left to right.

Level-order traversal is essentially breadth-first traversal, also known as breadth-first search (BFS), which embodies a \"expanding outward circle by circle\" layer-by-layer traversal method.

Figure 7-9   Level-order traversal of a binary tree

","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#1-code-implementation","level":3,"title":"1.   Code Implementation","text":"

Breadth-first traversal is typically implemented with the help of a \"queue\". The queue follows the \"first in, first out\" rule, while breadth-first traversal follows the \"layer-by-layer progression\" rule; the underlying ideas of the two are consistent. The implementation code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree_bfs.py
def level_order(root: TreeNode | None) -> list[int]:\n    \"\"\"Level-order traversal\"\"\"\n    # Initialize queue, add root node\n    queue: deque[TreeNode] = deque()\n    queue.append(root)\n    # Initialize a list to save the traversal sequence\n    res = []\n    while queue:\n        node: TreeNode = queue.popleft()  # Dequeue\n        res.append(node.val)  # Save node value\n        if node.left is not None:\n            queue.append(node.left)  # Left child node enqueue\n        if node.right is not None:\n            queue.append(node.right)  # Right child node enqueue\n    return res\n
binary_tree_bfs.cpp
/* Level-order traversal */\nvector<int> levelOrder(TreeNode *root) {\n    // Initialize queue, add root node\n    queue<TreeNode *> queue;\n    queue.push(root);\n    // Initialize a list to save the traversal sequence\n    vector<int> vec;\n    while (!queue.empty()) {\n        TreeNode *node = queue.front();\n        queue.pop();              // Dequeue\n        vec.push_back(node->val); // Save node value\n        if (node->left != nullptr)\n            queue.push(node->left); // Left child node enqueue\n        if (node->right != nullptr)\n            queue.push(node->right); // Right child node enqueue\n    }\n    return vec;\n}\n
binary_tree_bfs.java
/* Level-order traversal */\nList<Integer> levelOrder(TreeNode root) {\n    // Initialize queue, add root node\n    Queue<TreeNode> queue = new LinkedList<>();\n    queue.add(root);\n    // Initialize a list to save the traversal sequence\n    List<Integer> list = new ArrayList<>();\n    while (!queue.isEmpty()) {\n        TreeNode node = queue.poll(); // Dequeue\n        list.add(node.val);           // Save node value\n        if (node.left != null)\n            queue.offer(node.left);   // Left child node enqueue\n        if (node.right != null)\n            queue.offer(node.right);  // Right child node enqueue\n    }\n    return list;\n}\n
binary_tree_bfs.cs
/* Level-order traversal */\nList<int> LevelOrder(TreeNode root) {\n    // Initialize queue, add root node\n    Queue<TreeNode> queue = new();\n    queue.Enqueue(root);\n    // Initialize a list to save the traversal sequence\n    List<int> list = [];\n    while (queue.Count != 0) {\n        TreeNode node = queue.Dequeue(); // Dequeue\n        list.Add(node.val!.Value);       // Save node value\n        if (node.left != null)\n            queue.Enqueue(node.left);    // Left child node enqueue\n        if (node.right != null)\n            queue.Enqueue(node.right);   // Right child node enqueue\n    }\n    return list;\n}\n
binary_tree_bfs.go
/* Level-order traversal */\nfunc levelOrder(root *TreeNode) []any {\n    // Initialize queue, add root node\n    queue := list.New()\n    queue.PushBack(root)\n    // Initialize a slice to save traversal sequence\n    nums := make([]any, 0)\n    for queue.Len() > 0 {\n        // Dequeue\n        node := queue.Remove(queue.Front()).(*TreeNode)\n        // Save node value\n        nums = append(nums, node.Val)\n        if node.Left != nil {\n            // Left child node enqueue\n            queue.PushBack(node.Left)\n        }\n        if node.Right != nil {\n            // Right child node enqueue\n            queue.PushBack(node.Right)\n        }\n    }\n    return nums\n}\n
binary_tree_bfs.swift
/* Level-order traversal */\nfunc levelOrder(root: TreeNode) -> [Int] {\n    // Initialize queue, add root node\n    var queue: [TreeNode] = [root]\n    // Initialize a list to save the traversal sequence\n    var list: [Int] = []\n    while !queue.isEmpty {\n        let node = queue.removeFirst() // Dequeue\n        list.append(node.val) // Save node value\n        if let left = node.left {\n            queue.append(left) // Left child node enqueue\n        }\n        if let right = node.right {\n            queue.append(right) // Right child node enqueue\n        }\n    }\n    return list\n}\n
binary_tree_bfs.js
/* Level-order traversal */\nfunction levelOrder(root) {\n    // Initialize queue, add root node\n    const queue = [root];\n    // Initialize a list to save the traversal sequence\n    const list = [];\n    while (queue.length) {\n        let node = queue.shift(); // Dequeue\n        list.push(node.val); // Save node value\n        if (node.left) queue.push(node.left); // Left child node enqueue\n        if (node.right) queue.push(node.right); // Right child node enqueue\n    }\n    return list;\n}\n
binary_tree_bfs.ts
/* Level-order traversal */\nfunction levelOrder(root: TreeNode | null): number[] {\n    // Initialize queue, add root node\n    const queue = [root];\n    // Initialize a list to save the traversal sequence\n    const list: number[] = [];\n    while (queue.length) {\n        let node = queue.shift() as TreeNode; // Dequeue\n        list.push(node.val); // Save node value\n        if (node.left) {\n            queue.push(node.left); // Left child node enqueue\n        }\n        if (node.right) {\n            queue.push(node.right); // Right child node enqueue\n        }\n    }\n    return list;\n}\n
binary_tree_bfs.dart
/* Level-order traversal */\nList<int> levelOrder(TreeNode? root) {\n  // Initialize queue, add root node\n  Queue<TreeNode?> queue = Queue();\n  queue.add(root);\n  // Initialize a list to save the traversal sequence\n  List<int> res = [];\n  while (queue.isNotEmpty) {\n    TreeNode? node = queue.removeFirst(); // Dequeue\n    res.add(node!.val); // Save node value\n    if (node.left != null) queue.add(node.left); // Left child node enqueue\n    if (node.right != null) queue.add(node.right); // Right child node enqueue\n  }\n  return res;\n}\n
binary_tree_bfs.rs
/* Level-order traversal */\nfn level_order(root: &Rc<RefCell<TreeNode>>) -> Vec<i32> {\n    // Initialize queue, add root node\n    let mut que = VecDeque::new();\n    que.push_back(root.clone());\n    // Initialize a list to save the traversal sequence\n    let mut vec = Vec::new();\n\n    while let Some(node) = que.pop_front() {\n        // Dequeue\n        vec.push(node.borrow().val); // Save node value\n        if let Some(left) = node.borrow().left.as_ref() {\n            que.push_back(left.clone()); // Left child node enqueue\n        }\n        if let Some(right) = node.borrow().right.as_ref() {\n            que.push_back(right.clone()); // Right child node enqueue\n        };\n    }\n    vec\n}\n
binary_tree_bfs.c
/* Level-order traversal */\nint *levelOrder(TreeNode *root, int *size) {\n    /* Auxiliary queue */\n    int front, rear;\n    int index, *arr;\n    TreeNode *node;\n    TreeNode **queue;\n\n    /* Auxiliary queue */\n    queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE);\n    // Queue pointer\n    front = 0, rear = 0;\n    // Add root node\n    queue[rear++] = root;\n    // Initialize a list to save the traversal sequence\n    /* Auxiliary array */\n    arr = (int *)malloc(sizeof(int) * MAX_SIZE);\n    // Array pointer\n    index = 0;\n    while (front < rear) {\n        // Dequeue\n        node = queue[front++];\n        // Save node value\n        arr[index++] = node->val;\n        if (node->left != NULL) {\n            // Left child node enqueue\n            queue[rear++] = node->left;\n        }\n        if (node->right != NULL) {\n            // Right child node enqueue\n            queue[rear++] = node->right;\n        }\n    }\n    // Update array length value\n    *size = index;\n    arr = realloc(arr, sizeof(int) * (*size));\n\n    // Free auxiliary array space\n    free(queue);\n    return arr;\n}\n
binary_tree_bfs.kt
/* Level-order traversal */\nfun levelOrder(root: TreeNode?): MutableList<Int> {\n    // Initialize queue, add root node\n    val queue = LinkedList<TreeNode?>()\n    queue.add(root)\n    // Initialize a list to save the traversal sequence\n    val list = mutableListOf<Int>()\n    while (queue.isNotEmpty()) {\n        val node = queue.poll()      // Dequeue\n        list.add(node?._val!!)       // Save node value\n        if (node.left != null)\n            queue.offer(node.left)   // Left child node enqueue\n        if (node.right != null)\n            queue.offer(node.right)  // Right child node enqueue\n    }\n    return list\n}\n
binary_tree_bfs.rb
### Level-order traversal ###\ndef level_order(root)\n  # Initialize queue, add root node\n  queue = [root]\n  # Initialize a list to save the traversal sequence\n  res = []\n  while !queue.empty?\n    node = queue.shift # Dequeue\n    res << node.val # Save node value\n    queue << node.left unless node.left.nil? # Left child node enqueue\n    queue << node.right unless node.right.nil? # Right child node enqueue\n  end\n  res\nend\n
","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#2-complexity-analysis","level":3,"title":"2.   Complexity Analysis","text":"
  • Time complexity is \\(O(n)\\): All nodes are visited once, using \\(O(n)\\) time, where \\(n\\) is the number of nodes.
  • Space complexity is \\(O(n)\\): In the worst case, i.e., a full binary tree, before traversing to the bottom level, the queue contains at most \\((n + 1) / 2\\) nodes simultaneously, occupying \\(O(n)\\) space.
","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#722-preorder-inorder-and-postorder-traversal","level":2,"title":"7.2.2   Preorder, Inorder, and Postorder Traversal","text":"

Correspondingly, preorder, inorder, and postorder traversals all belong to depth-first traversal, also known as depth-first search (DFS), which embodies a \"first go to the end, then backtrack and continue\" traversal method.

Figure 7-10 shows how depth-first traversal works on a binary tree. Depth-first traversal is like \"walking\" around the perimeter of the entire binary tree, encountering three positions at each node, corresponding to preorder, inorder, and postorder traversal.

Figure 7-10   Preorder, inorder, and postorder traversal of a binary tree

","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#1-code-implementation_1","level":3,"title":"1.   Code Implementation","text":"

Depth-first search is usually implemented based on recursion:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree_dfs.py
def pre_order(root: TreeNode | None):\n    \"\"\"Preorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit priority: root node -> left subtree -> right subtree\n    res.append(root.val)\n    pre_order(root=root.left)\n    pre_order(root=root.right)\n\ndef in_order(root: TreeNode | None):\n    \"\"\"Inorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit priority: left subtree -> root node -> right subtree\n    in_order(root=root.left)\n    res.append(root.val)\n    in_order(root=root.right)\n\ndef post_order(root: TreeNode | None):\n    \"\"\"Postorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit priority: left subtree -> right subtree -> root node\n    post_order(root=root.left)\n    post_order(root=root.right)\n    res.append(root.val)\n
binary_tree_dfs.cpp
/* Preorder traversal */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit priority: root node -> left subtree -> right subtree\n    vec.push_back(root->val);\n    preOrder(root->left);\n    preOrder(root->right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root->left);\n    vec.push_back(root->val);\n    inOrder(root->right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root->left);\n    postOrder(root->right);\n    vec.push_back(root->val);\n}\n
binary_tree_dfs.java
/* Preorder traversal */\nvoid preOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit priority: root node -> left subtree -> right subtree\n    list.add(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root.left);\n    list.add(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root.left);\n    postOrder(root.right);\n    list.add(root.val);\n}\n
binary_tree_dfs.cs
/* Preorder traversal */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit priority: root node -> left subtree -> right subtree\n    list.Add(root.val!.Value);\n    PreOrder(root.left);\n    PreOrder(root.right);\n}\n\n/* Inorder traversal */\nvoid InOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit priority: left subtree -> root node -> right subtree\n    InOrder(root.left);\n    list.Add(root.val!.Value);\n    InOrder(root.right);\n}\n\n/* Postorder traversal */\nvoid PostOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit priority: left subtree -> right subtree -> root node\n    PostOrder(root.left);\n    PostOrder(root.right);\n    list.Add(root.val!.Value);\n}\n
binary_tree_dfs.go
/* Preorder traversal */\nfunc preOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit priority: root node -> left subtree -> right subtree\n    nums = append(nums, node.Val)\n    preOrder(node.Left)\n    preOrder(node.Right)\n}\n\n/* Inorder traversal */\nfunc inOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(node.Left)\n    nums = append(nums, node.Val)\n    inOrder(node.Right)\n}\n\n/* Postorder traversal */\nfunc postOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(node.Left)\n    postOrder(node.Right)\n    nums = append(nums, node.Val)\n}\n
binary_tree_dfs.swift
/* Preorder traversal */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit priority: root node -> left subtree -> right subtree\n    list.append(root.val)\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n}\n\n/* Inorder traversal */\nfunc inOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root: root.left)\n    list.append(root.val)\n    inOrder(root: root.right)\n}\n\n/* Postorder traversal */\nfunc postOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root: root.left)\n    postOrder(root: root.right)\n    list.append(root.val)\n}\n
binary_tree_dfs.js
/* Preorder traversal */\nfunction preOrder(root) {\n    if (root === null) return;\n    // Visit priority: root node -> left subtree -> right subtree\n    list.push(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nfunction inOrder(root) {\n    if (root === null) return;\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root.left);\n    list.push(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nfunction postOrder(root) {\n    if (root === null) return;\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root.left);\n    postOrder(root.right);\n    list.push(root.val);\n}\n
binary_tree_dfs.ts
/* Preorder traversal */\nfunction preOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit priority: root node -> left subtree -> right subtree\n    list.push(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nfunction inOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root.left);\n    list.push(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nfunction postOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root.left);\n    postOrder(root.right);\n    list.push(root.val);\n}\n
binary_tree_dfs.dart
/* Preorder traversal */\nvoid preOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit priority: root node -> left subtree -> right subtree\n  list.add(node.val);\n  preOrder(node.left);\n  preOrder(node.right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit priority: left subtree -> root node -> right subtree\n  inOrder(node.left);\n  list.add(node.val);\n  inOrder(node.right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit priority: left subtree -> right subtree -> root node\n  postOrder(node.left);\n  postOrder(node.right);\n  list.add(node.val);\n}\n
binary_tree_dfs.rs
/* Preorder traversal */\nfn pre_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit priority: root node -> left subtree -> right subtree\n            let node = node.borrow();\n            res.push(node.val);\n            dfs(node.left.as_ref(), res);\n            dfs(node.right.as_ref(), res);\n        }\n    }\n    dfs(root, &mut result);\n\n    result\n}\n\n/* Inorder traversal */\nfn in_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit priority: left subtree -> root node -> right subtree\n            let node = node.borrow();\n            dfs(node.left.as_ref(), res);\n            res.push(node.val);\n            dfs(node.right.as_ref(), res);\n        }\n    }\n    dfs(root, &mut result);\n\n    result\n}\n\n/* Postorder traversal */\nfn post_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit priority: left subtree -> right subtree -> root node\n            let node = node.borrow();\n            dfs(node.left.as_ref(), res);\n            dfs(node.right.as_ref(), res);\n            res.push(node.val);\n        }\n    }\n\n    dfs(root, &mut result);\n\n    result\n}\n
binary_tree_dfs.c
/* Preorder traversal */\nvoid preOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit priority: root node -> left subtree -> right subtree\n    arr[(*size)++] = root->val;\n    preOrder(root->left, size);\n    preOrder(root->right, size);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root->left, size);\n    arr[(*size)++] = root->val;\n    inOrder(root->right, size);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root->left, size);\n    postOrder(root->right, size);\n    arr[(*size)++] = root->val;\n}\n
binary_tree_dfs.kt
/* Preorder traversal */\nfun preOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit priority: root node -> left subtree -> right subtree\n    list.add(root._val)\n    preOrder(root.left)\n    preOrder(root.right)\n}\n\n/* Inorder traversal */\nfun inOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root.left)\n    list.add(root._val)\n    inOrder(root.right)\n}\n\n/* Postorder traversal */\nfun postOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root.left)\n    postOrder(root.right)\n    list.add(root._val)\n}\n
binary_tree_dfs.rb
### Pre-order traversal ###\ndef pre_order(root)\n  return if root.nil?\n\n  # Visit priority: root node -> left subtree -> right subtree\n  $res << root.val\n  pre_order(root.left)\n  pre_order(root.right)\nend\n\n### In-order traversal ###\ndef in_order(root)\n  return if root.nil?\n\n  # Visit priority: left subtree -> root node -> right subtree\n  in_order(root.left)\n  $res << root.val\n  in_order(root.right)\nend\n\n### Post-order traversal ###\ndef post_order(root)\n  return if root.nil?\n\n  # Visit priority: left subtree -> right subtree -> root node\n  post_order(root.left)\n  post_order(root.right)\n  $res << root.val\nend\n

Tip

Depth-first search can also be implemented based on iteration, interested readers can study this on their own.

Figure 7-11 shows the recursive process of preorder traversal of a binary tree, which can be divided into two opposite parts: \"recursion\" and \"return\".

  1. \"Recursion\" means opening a new method, where the program accesses the next node in this process.
  2. \"Return\" means the function returns, indicating that the current node has been fully visited.
<1><2><3><4><5><6><7><8><9><10><11>

Figure 7-11   The recursive process of preorder traversal

","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#2-complexity-analysis_1","level":3,"title":"2.   Complexity Analysis","text":"
  • Time complexity is \\(O(n)\\): All nodes are visited once, using \\(O(n)\\) time.
  • Space complexity is \\(O(n)\\): In the worst case, i.e., the tree degenerates into a linked list, the recursion depth reaches \\(n\\), and the system occupies \\(O(n)\\) stack frame space.
","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/summary/","level":1,"title":"7.6   Summary","text":"","path":["Chapter 7. Tree","7.6   Summary"],"tags":[]},{"location":"chapter_tree/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • A binary tree is a non-linear data structure that embodies the divide-and-conquer logic of \"one divides into two\". Each binary tree node contains a value and two pointers, which respectively point to its left and right child nodes.
  • For a certain node in a binary tree, the tree formed by its left (right) child node and all nodes below is called the left (right) subtree of that node.
  • Related terminology of binary trees includes root node, leaf node, level, degree, edge, height, and depth.
  • The initialization, node insertion, and node removal operations of binary trees are similar to those of linked lists.
  • Common types of binary trees include perfect binary trees, complete binary trees, full binary trees, and balanced binary trees. The perfect binary tree is the ideal state, while the linked list is the worst state after degradation.
  • A binary tree can be represented using an array by arranging node values and empty slots in level-order traversal sequence, and implementing pointers based on the index mapping relationship between parent and child nodes.
  • Level-order traversal of a binary tree is a breadth-first search method, embodying a layer-by-layer traversal approach of \"expanding outward circle by circle\", typically implemented using a queue.
  • Preorder, inorder, and postorder traversals all belong to depth-first search, embodying a traversal approach of \"first go to the end, then backtrack and continue\", typically implemented using recursion.
  • A binary search tree is an efficient data structure for element searching, with search, insertion, and removal operations all having time complexity of \\(O(\\log n)\\). When a binary search tree degenerates into a linked list, all time complexities degrade to \\(O(n)\\).
  • An AVL tree, also known as a balanced binary search tree, ensures the tree remains balanced after continuous node insertions and removals through rotation operations.
  • Rotation operations in AVL trees include right rotation, left rotation, left rotation then right rotation, and right rotation then left rotation. After inserting or removing nodes, AVL trees perform rotation operations from bottom to top to restore the tree to balance.
","path":["Chapter 7. Tree","7.6   Summary"],"tags":[]},{"location":"chapter_tree/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: For a binary tree with only one node, are both the height of the tree and the depth of the root node \\(0\\)?

Yes, because height and depth are typically defined as \"the number of edges passed.\"

Q: The insertion and removal in a binary tree are generally accomplished by a set of operations. What does \"a set of operations\" refer to here? Does it imply releasing the resources of the child nodes?

Taking the binary search tree as an example, the operation of removing a node needs to be handled in three different scenarios, each requiring multiple steps of node operations.

Q: Why does DFS traversal of binary trees have three orders: preorder, inorder, and postorder, and what are their uses?

Similar to forward and reverse traversal of arrays, preorder, inorder, and postorder traversals are three methods of binary tree traversal that allow us to obtain a traversal result in a specific order. For example, in a binary search tree, since nodes satisfy the relationship left child node value < root node value < right child node value, we only need to traverse the tree with the priority of \"left \\(\\rightarrow\\) root \\(\\rightarrow\\) right\" to obtain an ordered node sequence.

Q: In a right rotation operation handling the relationship between unbalanced nodes node, child, and grand_child, doesn't the connection between node and its parent node get lost after the right rotation?

We need to view this problem from a recursive perspective. The right rotation operation right_rotate(root) passes in the root node of the subtree and eventually returns the root node of the subtree after rotation with return child. The connection between the subtree's root node and its parent node is completed after the function returns, which is not within the maintenance scope of the right rotation operation.

Q: In C++, functions are divided into private and public sections. What considerations are there for this? Why are the height() function and the updateHeight() function placed in public and private, respectively?

It mainly depends on the method's usage scope. If a method is only used within the class, then it is designed as private. For example, calling updateHeight() alone by the user makes no sense, as it is only a step in insertion or removal operations. However, height() is used to access node height, similar to vector.size(), so it is set to public for ease of use.

Q: How do you build a binary search tree from a set of input data? Is the choice of root node very important?

Yes, the method for building a tree is provided in the build_tree() method in the binary search tree code. As for the choice of root node, we typically sort the input data, then select the middle element as the root node, and recursively build the left and right subtrees. This approach maximizes the tree's balance.

Q: In Java, do you always have to use the equals() method for string comparison?

In Java, for primitive data types, == is used to compare whether the values of two variables are equal. For reference types, the working principles of the two symbols are different.

  • ==: Used to compare whether two variables point to the same object, i.e., whether their positions in memory are the same.
  • equals(): Used to compare whether the values of two objects are equal.

Therefore, if we want to compare values, we should use equals(). However, strings initialized via String a = \"hi\"; String b = \"hi\"; are stored in the string constant pool and point to the same object, so a == b can also be used to compare the contents of the two strings.

Q: Before reaching the bottom level, is the number of nodes in the queue \\(2^h\\) in breadth-first traversal?

Yes, for example, a full binary tree with height \\(h = 2\\) has a total of \\(n = 7\\) nodes, then the bottom level has \\(4 = 2^h = (n + 1) / 2\\) nodes.

","path":["Chapter 7. Tree","7.6   Summary"],"tags":[]}]} \ No newline at end of file +{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"chapter_appendix/","level":1,"title":"Chapter 16.   Appendix","text":"","path":["Chapter 16. Appendix","Chapter 16.   Appendix"],"tags":[]},{"location":"chapter_appendix/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 16.1   Programming Environment Installation
  • 16.2   Contributing Together
  • 16.3   Terminology Table
","path":["Chapter 16. Appendix","Chapter 16.   Appendix"],"tags":[]},{"location":"chapter_appendix/contribution/","level":1,"title":"16.2   Contributing Together","text":"

Due to limited capacity, there may be inevitable omissions and errors in this book. We appreciate your understanding and are grateful for your help in correcting them. If you discover typos, broken links, missing content, ambiguous wording, unclear explanations, or structural issues, please help us make corrections to provide readers with higher-quality learning resources.

The GitHub IDs of all contributors will be displayed on the homepage of the book repository, the web version, and the PDF version to acknowledge their selfless contributions to the open source community.

The Charm of Open Source

The interval between two printings of a physical book is often quite long, making content updates very inconvenient.

In this open source book, the time for content updates has been shortened to just days or even hours.

","path":["Chapter 16. Appendix","16.2   Contributing Together"],"tags":[]},{"location":"chapter_appendix/contribution/#1-minor-content-adjustments","level":3,"title":"1.   Minor Content Adjustments","text":"

As shown in Figure 16-3, there is an \"edit icon\" in the top-right corner of each page. You can modify text or code by following these steps.

  1. Click the \"edit icon\". If you encounter a prompt asking you to \"Fork this repository\", please approve the operation.
  2. Modify the content of the Markdown source file, verify the correctness of the content, and maintain consistent formatting as much as possible.
  3. Fill in a description of your changes at the bottom of the page, then click the \"Propose file change\" button. After the page transitions, click the \"Create pull request\" button to submit your pull request.

Figure 16-3   Page edit button

Images cannot be directly modified. Please describe the issue by creating a new Issue or leaving a comment. We will promptly redraw and replace the images.

","path":["Chapter 16. Appendix","16.2   Contributing Together"],"tags":[]},{"location":"chapter_appendix/contribution/#2-content-creation","level":3,"title":"2.   Content Creation","text":"

If you are interested in contributing to this open source project, including translating code into other programming languages or expanding article content, you will need to follow the Pull Request workflow below.

  1. Log in to GitHub and Fork the book's code repository to your personal account.
  2. Enter your forked repository webpage and use the git clone command to clone the repository to your local machine.
  3. Create content locally and conduct comprehensive tests to verify code correctness.
  4. Commit your local changes and push them to the remote repository.
  5. Refresh the repository webpage and click the \"Create pull request\" button to submit your pull request.
","path":["Chapter 16. Appendix","16.2   Contributing Together"],"tags":[]},{"location":"chapter_appendix/contribution/#3-docker-deployment","level":3,"title":"3.   Docker Deployment","text":"

From the root directory of hello-algo, run the following Docker script to access the project at http://localhost:8000:

docker-compose up -d\n

Use the following command to remove the deployment:

docker-compose down\n
","path":["Chapter 16. Appendix","16.2   Contributing Together"],"tags":[]},{"location":"chapter_appendix/installation/","level":1,"title":"16.1   Programming Environment Installation","text":"","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#1611-installing-ide","level":2,"title":"16.1.1   Installing Ide","text":"

We recommend using the open-source and lightweight VS Code as the local integrated development environment (IDE). Visit the VS Code official website, and download and install the appropriate version of VS Code according to your operating system.

Figure 16-1   Download VS Code from the Official Website

VS Code has a powerful ecosystem of extensions that supports running and debugging most programming languages. For example, after installing the \"Python Extension Pack\" extension, you can debug Python code. The installation steps are shown in the following figure.

Figure 16-2   Install VS Code Extensions

","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#1612-installing-language-environments","level":2,"title":"16.1.2   Installing Language Environments","text":"","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#1-python-environment","level":3,"title":"1.   Python Environment","text":"
  1. Download and install Miniconda3, which requires Python 3.10 or newer.
  2. Search for python in the VS Code extension marketplace and install the Python Extension Pack.
  3. (Optional) Enter pip install black on the command line to install the code formatter.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#2-cc-environment","level":3,"title":"2.   C/c++ Environment","text":"
  1. Windows systems need to install MinGW (configuration tutorial); macOS comes with Clang built-in and does not require installation.
  2. Search for c++ in the VS Code extension marketplace and install the C/C++ Extension Pack.
  3. (Optional) Open the Settings page, search for the Clang_format_fallback Style code formatting option, and set it to { BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#3-java-environment","level":3,"title":"3.   Java Environment","text":"
  1. Download and install OpenJDK (version must be > JDK 9).
  2. Search for java in the VS Code extension marketplace and install the Extension Pack for Java.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#4-c-environment","level":3,"title":"4.   C# Environment","text":"
  1. Download and install .Net 8.0.
  2. Search for C# Dev Kit in the VS Code extension marketplace and install C# Dev Kit (configuration tutorial).
  3. You can also use Visual Studio (installation tutorial).
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#5-go-environment","level":3,"title":"5.   Go Environment","text":"
  1. Download and install Go.
  2. Search for go in the VS Code extension marketplace and install Go.
  3. Press Ctrl + Shift + P to open the command palette, type go, select Go: Install/Update Tools, check all options and install.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#6-swift-environment","level":3,"title":"6.   Swift Environment","text":"
  1. Download and install Swift.
  2. Search for swift in the VS Code extension marketplace and install Swift for Visual Studio Code.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#7-javascript-environment","level":3,"title":"7.   Javascript Environment","text":"
  1. Download and install Node.js.
  2. (Optional) Search for Prettier in the VS Code extension marketplace and install the code formatter.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#8-typescript-environment","level":3,"title":"8.   Typescript Environment","text":"
  1. Follow the same installation steps as the JavaScript environment.
  2. Install TypeScript Execute (tsx).
  3. Search for typescript in the VS Code extension marketplace and install Pretty TypeScript Errors.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#9-dart-environment","level":3,"title":"9.   Dart Environment","text":"
  1. Download and install Dart.
  2. Search for dart in the VS Code extension marketplace and install Dart.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/installation/#10-rust-environment","level":3,"title":"10.   Rust Environment","text":"
  1. Download and install Rust.
  2. Search for rust in the VS Code extension marketplace and install rust-analyzer.
","path":["Chapter 16. Appendix","16.1   Programming Environment Installation"],"tags":[]},{"location":"chapter_appendix/terminology/","level":1,"title":"16.3   Terminology Table","text":"

The following table lists important terms that appear in this book. It is worth noting the following points:

  • We recommend remembering the English names of terms to help with reading English literature.
  • Some terms have different names in Simplified Chinese and Traditional Chinese.

Table 16-1   Important Terms in Data Structures and Algorithms

English Simplified Chinese Traditional Chinese algorithm 算法 演算法 data structure 数据结构 資料結構 code 代码 程式碼 file 文件 檔案 function 函数 函式 method 方法 方法 variable 变量 變數 asymptotic complexity analysis 渐近复杂度分析 漸近複雜度分析 time complexity 时间复杂度 時間複雜度 space complexity 空间复杂度 空間複雜度 loop 循环 迴圈 iteration 迭代 迭代 recursion 递归 遞迴 tail recursion 尾递归 尾遞迴 recursion tree 递归树 遞迴樹 big-\\(O\\) notation 大 \\(O\\) 记号 大 \\(O\\) 記號 asymptotic upper bound 渐近上界 漸近上界 sign-magnitude 原码 原碼 1’s complement 反码 一補數 2’s complement 补码 二補數 array 数组 陣列 index 索引 索引 linked list 链表 鏈結串列 linked list node, list node 链表节点 鏈結串列節點 head node 头节点 頭節點 tail node 尾节点 尾節點 list 列表 串列 dynamic array 动态数组 動態陣列 hard disk 硬盘 硬碟 random-access memory (RAM) 内存 記憶體 cache memory 缓存 快取 cache miss 缓存未命中 快取未命中 cache hit rate 缓存命中率 快取命中率 stack 栈 堆疊 top of the stack 栈顶 堆疊頂 bottom of the stack 栈底 堆疊底 queue 队列 佇列 double-ended queue 双向队列 雙向佇列 front of the queue 队首 佇列首 rear of the queue 队尾 佇列尾 hash table 哈希表 雜湊表 hash set 哈希集合 雜湊集合 bucket 桶 桶 hash function 哈希函数 雜湊函式 hash collision 哈希冲突 雜湊衝突 load factor 负载因子 負載因子 separate chaining 链式地址 鏈結位址 open addressing 开放寻址 開放定址 linear probing 线性探测 線性探查 lazy deletion 懒删除 懶刪除 binary tree 二叉树 二元樹 tree node 树节点 樹節點 left-child node 左子节点 左子節點 right-child node 右子节点 右子節點 parent node 父节点 父節點 left subtree 左子树 左子樹 right subtree 右子树 右子樹 root node 根节点 根節點 leaf node 叶节点 葉節點 edge 边 邊 level 层 層 degree 度 度 height 高度 高度 depth 深度 深度 perfect binary tree 完美二叉树 完美二元樹 complete binary tree 完全二叉树 完全二元樹 full binary tree 完满二叉树 完滿二元樹 balanced binary tree 平衡二叉树 平衡二元樹 binary search tree 二叉搜索树 二元搜尋樹 AVL tree AVL 树 AVL 樹 red-black tree 红黑树 紅黑樹 level-order traversal 层序遍历 層序走訪 breadth-first traversal 广度优先遍历 廣度優先走訪 depth-first traversal 深度优先遍历 深度優先走訪 binary search tree 二叉搜索树 二元搜尋樹 balanced binary search tree 平衡二叉搜索树 平衡二元搜尋樹 balance factor 平衡因子 平衡因子 heap 堆 堆積 max heap 大顶堆 大頂堆積 min heap 小顶堆 小頂堆積 priority queue 优先队列 優先佇列 heapify 堆化 堆積化 top-\\(k\\) problem Top-\\(k\\) 问题 Top-\\(k\\) 問題 graph 图 圖 vertex 顶点 頂點 undirected graph 无向图 無向圖 directed graph 有向图 有向圖 connected graph 连通图 連通圖 disconnected graph 非连通图 非連通圖 weighted graph 有权图 有權圖 adjacency 邻接 鄰接 path 路径 路徑 in-degree 入度 入度 out-degree 出度 出度 adjacency matrix 邻接矩阵 鄰接矩陣 adjacency list 邻接表 鄰接表 breadth-first search 广度优先搜索 廣度優先搜尋 depth-first search 深度优先搜索 深度優先搜尋 binary search 二分查找 二分搜尋 searching algorithm 搜索算法 搜尋演算法 sorting algorithm 排序算法 排序演算法 selection sort 选择排序 選擇排序 bubble sort 冒泡排序 泡沫排序 insertion sort 插入排序 插入排序 quick sort 快速排序 快速排序 merge sort 归并排序 合併排序 heap sort 堆排序 堆積排序 bucket sort 桶排序 桶排序 counting sort 计数排序 計數排序 radix sort 基数排序 基數排序 divide and conquer 分治 分治 hanota problem 汉诺塔问题 河內塔問題 backtracking algorithm 回溯算法 回溯演算法 constraint 约束 約束 solution 解 解 state 状态 狀態 pruning 剪枝 剪枝 permutations problem 全排列问题 全排列問題 subset-sum problem 子集和问题 子集合問題 \\(n\\)-queens problem \\(n\\) 皇后问题 \\(n\\) 皇后問題 dynamic programming 动态规划 動態規劃 initial state 初始状态 初始狀態 state-transition equation 状态转移方程 狀態轉移方程 knapsack problem 背包问题 背包問題 edit distance problem 编辑距离问题 編輯距離問題 greedy algorithm 贪心算法 貪婪演算法","path":["Chapter 16. Appendix","16.3   Terminology Table"],"tags":[]},{"location":"chapter_array_and_linkedlist/","level":1,"title":"Chapter 4.   Array and Linked List","text":"

Abstract

The world of data structures is like a solid brick wall.

Array bricks are neatly arranged, tightly packed one by one. Linked list bricks are scattered everywhere, with connecting vines freely weaving through the gaps between bricks.

","path":["Chapter 4. Array and Linked List","Chapter 4.   Array and Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 4.1   Array
  • 4.2   Linked List
  • 4.3   List
  • 4.4   Memory and Cache *
  • 4.5   Summary
","path":["Chapter 4. Array and Linked List","Chapter 4.   Array and Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/","level":1,"title":"4.1   Array","text":"

An array is a linear data structure that stores elements of the same type in contiguous memory space. The position of an element in the array is called the element's index. Figure 4-1 illustrates the main concepts and storage method of arrays.

Figure 4-1   Array definition and storage method

","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#411-common-array-operations","level":2,"title":"4.1.1   Common Array Operations","text":"","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#1-initializing-arrays","level":3,"title":"1.   Initializing Arrays","text":"

We can choose between two array initialization methods based on our needs: without initial values or with given initial values. When no initial values are specified, most programming languages will initialize array elements to \\(0\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
# Initialize array\narr: list[int] = [0] * 5  # [ 0, 0, 0, 0, 0 ]\nnums: list[int] = [1, 3, 2, 5, 4]\n
array.cpp
/* Initialize array */\n// Stored on stack\nint arr[5];\nint nums[5] = { 1, 3, 2, 5, 4 };\n// Stored on heap (requires manual memory release)\nint* arr1 = new int[5];\nint* nums1 = new int[5] { 1, 3, 2, 5, 4 };\n
array.java
/* Initialize array */\nint[] arr = new int[5]; // { 0, 0, 0, 0, 0 }\nint[] nums = { 1, 3, 2, 5, 4 };\n
array.cs
/* Initialize array */\nint[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ]\nint[] nums = [1, 3, 2, 5, 4];\n
array.go
/* Initialize array */\nvar arr [5]int\n// In Go, specifying length ([5]int) creates an array; not specifying length ([]int) creates a slice\n// Since Go's arrays are designed to have their length determined at compile time, only constants can be used to specify the length\n// For convenience in implementing the extend() method, slices are treated as arrays below\nnums := []int{1, 3, 2, 5, 4}\n
array.swift
/* Initialize array */\nlet arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]\nlet nums = [1, 3, 2, 5, 4]\n
array.js
/* Initialize array */\nvar arr = new Array(5).fill(0);\nvar nums = [1, 3, 2, 5, 4];\n
array.ts
/* Initialize array */\nlet arr: number[] = new Array(5).fill(0);\nlet nums: number[] = [1, 3, 2, 5, 4];\n
array.dart
/* Initialize array */\nList<int> arr = List.filled(5, 0); // [0, 0, 0, 0, 0]\nList<int> nums = [1, 3, 2, 5, 4];\n
array.rs
/* Initialize array */\nlet arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0]\nlet slice: &[i32] = &[0; 5];\n// In Rust, specifying length ([i32; 5]) creates an array; not specifying length (&[i32]) creates a slice\n// Since Rust's arrays are designed to have their length determined at compile time, only constants can be used to specify the length\n// Vector is the type generally used as a dynamic array in Rust\n// For convenience in implementing the extend() method, vectors are treated as arrays below\nlet nums: Vec<i32> = vec![1, 3, 2, 5, 4];\n
array.c
/* Initialize array */\nint arr[5] = { 0 }; // { 0, 0, 0, 0, 0 }\nint nums[5] = { 1, 3, 2, 5, 4 };\n
array.kt
/* Initialize array */\nvar arr = IntArray(5) // { 0, 0, 0, 0, 0 }\nvar nums = intArrayOf(1, 3, 2, 5, 4)\n
array.rb
# Initialize array\narr = Array.new(5, 0)\nnums = [1, 3, 2, 5, 4]\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#2-accessing-elements","level":3,"title":"2.   Accessing Elements","text":"

Array elements are stored in contiguous memory space, which means calculating the memory address of array elements is very easy. Given the array's memory address (the memory address of the first element) and an element's index, we can use the formula shown in Figure 4-2 to calculate the element's memory address and directly access that element.

Figure 4-2   Memory address calculation for array elements

Observing Figure 4-2, we find that the first element of an array has an index of \\(0\\), which may seem counterintuitive since counting from \\(1\\) would be more natural. However, from the perspective of the address calculation formula, an index is essentially an offset from the memory address. The address offset of the first element is \\(0\\), so it is reasonable for its index to be \\(0\\).

Accessing elements in an array is highly efficient; we can randomly access any element in the array in \\(O(1)\\) time.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def random_access(nums: list[int]) -> int:\n    \"\"\"Random access to element\"\"\"\n    # Randomly select a number from the interval [0, len(nums)-1]\n    random_index = random.randint(0, len(nums) - 1)\n    # Retrieve and return the random element\n    random_num = nums[random_index]\n    return random_num\n
array.cpp
/* Random access to element */\nint randomAccess(int *nums, int size) {\n    // Randomly select a number from interval [0, size)\n    int randomIndex = rand() % size;\n    // Retrieve and return the random element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
array.java
/* Random access to element */\nint randomAccess(int[] nums) {\n    // Randomly select a number in the interval [0, nums.length)\n    int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length);\n    // Retrieve and return the random element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
array.cs
/* Random access to element */\nint RandomAccess(int[] nums) {\n    Random random = new();\n    // Randomly select a number in interval [0, nums.Length)\n    int randomIndex = random.Next(nums.Length);\n    // Retrieve and return the random element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
array.go
/* Random access to element */\nfunc randomAccess(nums []int) (randomNum int) {\n    // Randomly select a number in the interval [0, nums.length)\n    randomIndex := rand.Intn(len(nums))\n    // Retrieve and return the random element\n    randomNum = nums[randomIndex]\n    return\n}\n
array.swift
/* Random access to element */\nfunc randomAccess(nums: [Int]) -> Int {\n    // Randomly select a number in interval [0, nums.count)\n    let randomIndex = nums.indices.randomElement()!\n    // Retrieve and return the random element\n    let randomNum = nums[randomIndex]\n    return randomNum\n}\n
array.js
/* Random access to element */\nfunction randomAccess(nums) {\n    // Randomly select a number in the interval [0, nums.length)\n    const random_index = Math.floor(Math.random() * nums.length);\n    // Retrieve and return the random element\n    const random_num = nums[random_index];\n    return random_num;\n}\n
array.ts
/* Random access to element */\nfunction randomAccess(nums: number[]): number {\n    // Randomly select a number in the interval [0, nums.length)\n    const random_index = Math.floor(Math.random() * nums.length);\n    // Retrieve and return the random element\n    const random_num = nums[random_index];\n    return random_num;\n}\n
array.dart
/* Random access to element */\nint randomAccess(List<int> nums) {\n  // Randomly select a number in the interval [0, nums.length)\n  int randomIndex = Random().nextInt(nums.length);\n  // Retrieve and return the random element\n  int randomNum = nums[randomIndex];\n  return randomNum;\n}\n
array.rs
/* Random access to element */\nfn random_access(nums: &[i32]) -> i32 {\n    // Randomly select a number in interval [0, nums.len())\n    let random_index = rand::thread_rng().gen_range(0..nums.len());\n    // Retrieve and return the random element\n    let random_num = nums[random_index];\n    random_num\n}\n
array.c
/* Random access to element */\nint randomAccess(int *nums, int size) {\n    // Randomly select a number from interval [0, size)\n    int randomIndex = rand() % size;\n    // Retrieve and return the random element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
array.kt
/* Random access to element */\nfun randomAccess(nums: IntArray): Int {\n    // Randomly select a number in interval [0, nums.size)\n    val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size)\n    // Retrieve and return the random element\n    val randomNum = nums[randomIndex]\n    return randomNum\n}\n
array.rb
### Random access element ###\ndef random_access(nums)\n  # Randomly select a number in the interval [0, nums.length)\n  random_index = Random.rand(0...nums.length)\n\n  # Retrieve and return the random element\n  nums[random_index]\nend\n
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#3-inserting-elements","level":3,"title":"3.   Inserting Elements","text":"

Array elements are stored \"tightly adjacent\" in memory, with no space between them to store any additional data. As shown in Figure 4-3, if we want to insert an element in the middle of an array, we need to shift all elements after that position backward by one position, and then assign the value to that index.

Figure 4-3   Example of inserting an element into an array

It is worth noting that since the length of an array is fixed, inserting an element will inevitably cause the element at the end of the array to be \"lost\". We will leave the solution to this problem for discussion in the \"List\" chapter.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def insert(nums: list[int], num: int, index: int):\n    \"\"\"Insert element num at index index in the array\"\"\"\n    # Move all elements at and after index index backward by one position\n    for i in range(len(nums) - 1, index, -1):\n        nums[i] = nums[i - 1]\n    # Assign num to the element at index index\n    nums[index] = num\n
array.cpp
/* Insert element num at index index in the array */\nvoid insert(int *nums, int size, int num, int index) {\n    // Move all elements at and after index index backward by one position\n    for (int i = size - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.java
/* Insert element num at index index in the array */\nvoid insert(int[] nums, int num, int index) {\n    // Move all elements at and after index index backward by one position\n    for (int i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.cs
/* Insert element num at index index in the array */\nvoid Insert(int[] nums, int num, int index) {\n    // Move all elements at and after index index backward by one position\n    for (int i = nums.Length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.go
/* Insert element num at index index in the array */\nfunc insert(nums []int, num int, index int) {\n    // Move all elements at and after index index backward by one position\n    for i := len(nums) - 1; i > index; i-- {\n        nums[i] = nums[i-1]\n    }\n    // Assign num to the element at index index\n    nums[index] = num\n}\n
array.swift
/* Insert element num at index index in the array */\nfunc insert(nums: inout [Int], num: Int, index: Int) {\n    // Move all elements at and after index index backward by one position\n    for i in nums.indices.dropFirst(index).reversed() {\n        nums[i] = nums[i - 1]\n    }\n    // Assign num to the element at index index\n    nums[index] = num\n}\n
array.js
/* Insert element num at index index in the array */\nfunction insert(nums, num, index) {\n    // Move all elements at and after index index backward by one position\n    for (let i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.ts
/* Insert element num at index index in the array */\nfunction insert(nums: number[], num: number, index: number): void {\n    // Move all elements at and after index index backward by one position\n    for (let i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.dart
/* Insert element _num at array index index */\nvoid insert(List<int> nums, int _num, int index) {\n  // Move all elements at and after index index backward by one position\n  for (var i = nums.length - 1; i > index; i--) {\n    nums[i] = nums[i - 1];\n  }\n  // Assign _num to element at index\n  nums[index] = _num;\n}\n
array.rs
/* Insert element num at index index in the array */\nfn insert(nums: &mut [i32], num: i32, index: usize) {\n    // Move all elements at and after index index backward by one position\n    for i in (index + 1..nums.len()).rev() {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.c
/* Insert element num at index index in the array */\nvoid insert(int *nums, int size, int num, int index) {\n    // Move all elements at and after index index backward by one position\n    for (int i = size - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index index\n    nums[index] = num;\n}\n
array.kt
/* Insert element num at index index in the array */\nfun insert(nums: IntArray, num: Int, index: Int) {\n    // Move all elements at and after index index backward by one position\n    for (i in nums.size - 1 downTo index + 1) {\n        nums[i] = nums[i - 1]\n    }\n    // Assign num to the element at index index\n    nums[index] = num\n}\n
array.rb
### Insert element num at index in array ###\ndef insert(nums, num, index)\n  # Move all elements at and after index index backward by one position\n  for i in (nums.length - 1).downto(index + 1)\n    nums[i] = nums[i - 1]\n  end\n\n  # Assign num to the element at index index\n  nums[index] = num\nend\n
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#4-removing-elements","level":3,"title":"4.   Removing Elements","text":"

Similarly, as shown in Figure 4-4, to delete the element at index \\(i\\), we need to shift all elements after index \\(i\\) forward by one position.

Figure 4-4   Example of removing an element from an array

Note that after the deletion is complete, the original last element becomes \"meaningless\", so we do not need to specifically modify it.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def remove(nums: list[int], index: int):\n    \"\"\"Remove the element at index index\"\"\"\n    # Move all elements after index index forward by one position\n    for i in range(index, len(nums) - 1):\n        nums[i] = nums[i + 1]\n
array.cpp
/* Remove the element at index index */\nvoid remove(int *nums, int size, int index) {\n    // Move all elements after index index forward by one position\n    for (int i = index; i < size - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.java
/* Remove the element at index index */\nvoid remove(int[] nums, int index) {\n    // Move all elements after index index forward by one position\n    for (int i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.cs
/* Remove the element at index index */\nvoid Remove(int[] nums, int index) {\n    // Move all elements after index index forward by one position\n    for (int i = index; i < nums.Length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.go
/* Remove the element at index index */\nfunc remove(nums []int, index int) {\n    // Move all elements after index index forward by one position\n    for i := index; i < len(nums)-1; i++ {\n        nums[i] = nums[i+1]\n    }\n}\n
array.swift
/* Remove the element at index index */\nfunc remove(nums: inout [Int], index: Int) {\n    // Move all elements after index index forward by one position\n    for i in nums.indices.dropFirst(index).dropLast() {\n        nums[i] = nums[i + 1]\n    }\n}\n
array.js
/* Remove the element at index index */\nfunction remove(nums, index) {\n    // Move all elements after index index forward by one position\n    for (let i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.ts
/* Remove the element at index index */\nfunction remove(nums: number[], index: number): void {\n    // Move all elements after index index forward by one position\n    for (let i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.dart
/* Remove the element at index index */\nvoid remove(List<int> nums, int index) {\n  // Move all elements after index index forward by one position\n  for (var i = index; i < nums.length - 1; i++) {\n    nums[i] = nums[i + 1];\n  }\n}\n
array.rs
/* Remove the element at index index */\nfn remove(nums: &mut [i32], index: usize) {\n    // Move all elements after index index forward by one position\n    for i in index..nums.len() - 1 {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.c
/* Remove the element at index index */\n// Note: stdio.h occupies the remove keyword\nvoid removeItem(int *nums, int size, int index) {\n    // Move all elements after index index forward by one position\n    for (int i = index; i < size - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
array.kt
/* Remove the element at index index */\nfun remove(nums: IntArray, index: Int) {\n    // Move all elements after index index forward by one position\n    for (i in index..<nums.size - 1) {\n        nums[i] = nums[i + 1]\n    }\n}\n
array.rb
### Delete element at index ###\ndef remove(nums, index)\n  # Move all elements after index index forward by one position\n  for i in index...(nums.length - 1)\n    nums[i] = nums[i + 1]\n  end\nend\n

Overall, array insertion and deletion operations have the following drawbacks:

  • High time complexity: The average time complexity for both insertion and deletion in arrays is \\(O(n)\\), where \\(n\\) is the length of the array.
  • Loss of elements: Since the length of an array is immutable, after inserting an element, elements that exceed the array's length will be lost.
  • Memory waste: We can initialize a relatively long array and only use the front portion, so that when inserting data, the lost elements at the end are \"meaningless\", but this causes some memory space to be wasted.
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#5-traversing-arrays","level":3,"title":"5.   Traversing Arrays","text":"

In most programming languages, we can traverse an array either by index or by directly iterating through each element in the array:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def traverse(nums: list[int]):\n    \"\"\"Traverse array\"\"\"\n    count = 0\n    # Traverse array by index\n    for i in range(len(nums)):\n        count += nums[i]\n    # Direct traversal of array elements\n    for num in nums:\n        count += num\n    # Traverse simultaneously data index and elements\n    for i, num in enumerate(nums):\n        count += nums[i]\n        count += num\n
array.cpp
/* Traverse array */\nvoid traverse(int *nums, int size) {\n    int count = 0;\n    // Traverse array by index\n    for (int i = 0; i < size; i++) {\n        count += nums[i];\n    }\n}\n
array.java
/* Traverse array */\nvoid traverse(int[] nums) {\n    int count = 0;\n    // Traverse array by index\n    for (int i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Direct traversal of array elements\n    for (int num : nums) {\n        count += num;\n    }\n}\n
array.cs
/* Traverse array */\nvoid Traverse(int[] nums) {\n    int count = 0;\n    // Traverse array by index\n    for (int i = 0; i < nums.Length; i++) {\n        count += nums[i];\n    }\n    // Direct traversal of array elements\n    foreach (int num in nums) {\n        count += num;\n    }\n}\n
array.go
/* Traverse array */\nfunc traverse(nums []int) {\n    count := 0\n    // Traverse array by index\n    for i := 0; i < len(nums); i++ {\n        count += nums[i]\n    }\n    count = 0\n    // Direct traversal of array elements\n    for _, num := range nums {\n        count += num\n    }\n    // Traverse simultaneously data index and elements\n    for i, num := range nums {\n        count += nums[i]\n        count += num\n    }\n}\n
array.swift
/* Traverse array */\nfunc traverse(nums: [Int]) {\n    var count = 0\n    // Traverse array by index\n    for i in nums.indices {\n        count += nums[i]\n    }\n    // Direct traversal of array elements\n    for num in nums {\n        count += num\n    }\n    // Traverse simultaneously data index and elements\n    for (i, num) in nums.enumerated() {\n        count += nums[i]\n        count += num\n    }\n}\n
array.js
/* Traverse array */\nfunction traverse(nums) {\n    let count = 0;\n    // Traverse array by index\n    for (let i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Direct traversal of array elements\n    for (const num of nums) {\n        count += num;\n    }\n}\n
array.ts
/* Traverse array */\nfunction traverse(nums: number[]): void {\n    let count = 0;\n    // Traverse array by index\n    for (let i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Direct traversal of array elements\n    for (const num of nums) {\n        count += num;\n    }\n}\n
array.dart
/* Traverse array elements */\nvoid traverse(List<int> nums) {\n  int count = 0;\n  // Traverse array by index\n  for (var i = 0; i < nums.length; i++) {\n    count += nums[i];\n  }\n  // Direct traversal of array elements\n  for (int _num in nums) {\n    count += _num;\n  }\n  // Traverse array using forEach method\n  nums.forEach((_num) {\n    count += _num;\n  });\n}\n
array.rs
/* Traverse array */\nfn traverse(nums: &[i32]) {\n    let mut _count = 0;\n    // Traverse array by index\n    for i in 0..nums.len() {\n        _count += nums[i];\n    }\n    // Direct traversal of array elements\n    _count = 0;\n    for &num in nums {\n        _count += num;\n    }\n}\n
array.c
/* Traverse array */\nvoid traverse(int *nums, int size) {\n    int count = 0;\n    // Traverse array by index\n    for (int i = 0; i < size; i++) {\n        count += nums[i];\n    }\n}\n
array.kt
/* Traverse array */\nfun traverse(nums: IntArray) {\n    var count = 0\n    // Traverse array by index\n    for (i in nums.indices) {\n        count += nums[i]\n    }\n    // Direct traversal of array elements\n    for (j in nums) {\n        count += j\n    }\n}\n
array.rb
### Traverse array ###\ndef traverse(nums)\n  count = 0\n\n  # Traverse array by index\n  for i in 0...nums.length\n    count += nums[i]\n  end\n\n  # Direct traversal of array elements\n  for num in nums\n    count += num\n  end\nend\n
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#6-finding-elements","level":3,"title":"6.   Finding Elements","text":"

Finding a specified element in an array requires traversing the array and checking whether the element value matches in each iteration; if it matches, output the corresponding index.

Since an array is a linear data structure, the above search operation is called a \"linear search\".

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def find(nums: list[int], target: int) -> int:\n    \"\"\"Find the specified element in the array\"\"\"\n    for i in range(len(nums)):\n        if nums[i] == target:\n            return i\n    return -1\n
array.cpp
/* Find the specified element in the array */\nint find(int *nums, int size, int target) {\n    for (int i = 0; i < size; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
array.java
/* Find the specified element in the array */\nint find(int[] nums, int target) {\n    for (int i = 0; i < nums.length; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
array.cs
/* Find the specified element in the array */\nint Find(int[] nums, int target) {\n    for (int i = 0; i < nums.Length; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
array.go
/* Find the specified element in the array */\nfunc find(nums []int, target int) (index int) {\n    index = -1\n    for i := 0; i < len(nums); i++ {\n        if nums[i] == target {\n            index = i\n            break\n        }\n    }\n    return\n}\n
array.swift
/* Find the specified element in the array */\nfunc find(nums: [Int], target: Int) -> Int {\n    for i in nums.indices {\n        if nums[i] == target {\n            return i\n        }\n    }\n    return -1\n}\n
array.js
/* Find the specified element in the array */\nfunction find(nums, target) {\n    for (let i = 0; i < nums.length; i++) {\n        if (nums[i] === target) return i;\n    }\n    return -1;\n}\n
array.ts
/* Find the specified element in the array */\nfunction find(nums: number[], target: number): number {\n    for (let i = 0; i < nums.length; i++) {\n        if (nums[i] === target) {\n            return i;\n        }\n    }\n    return -1;\n}\n
array.dart
/* Find the specified element in the array */\nint find(List<int> nums, int target) {\n  for (var i = 0; i < nums.length; i++) {\n    if (nums[i] == target) return i;\n  }\n  return -1;\n}\n
array.rs
/* Find the specified element in the array */\nfn find(nums: &[i32], target: i32) -> Option<usize> {\n    for i in 0..nums.len() {\n        if nums[i] == target {\n            return Some(i);\n        }\n    }\n    None\n}\n
array.c
/* Find the specified element in the array */\nint find(int *nums, int size, int target) {\n    for (int i = 0; i < size; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
array.kt
/* Find the specified element in the array */\nfun find(nums: IntArray, target: Int): Int {\n    for (i in nums.indices) {\n        if (nums[i] == target)\n            return i\n    }\n    return -1\n}\n
array.rb
### Find specified element in array ###\ndef find(nums, target)\n  for i in 0...nums.length\n    return i if nums[i] == target\n  end\n\n  -1\nend\n
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#7-expanding-arrays","level":3,"title":"7.   Expanding Arrays","text":"

In complex system environments, programs cannot guarantee that the memory space after an array is available, making it unsafe to expand the array's capacity. Therefore, in most programming languages, the length of an array is immutable.

If we want to expand an array, we need to create a new, larger array and then copy the original array elements to the new array one by one. This is an \\(O(n)\\) operation, which is very time-consuming when the array is large. The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
def extend(nums: list[int], enlarge: int) -> list[int]:\n    \"\"\"Extend array length\"\"\"\n    # Initialize an array with extended length\n    res = [0] * (len(nums) + enlarge)\n    # Copy all elements from the original array to the new array\n    for i in range(len(nums)):\n        res[i] = nums[i]\n    # Return the extended new array\n    return res\n
array.cpp
/* Extend array length */\nint *extend(int *nums, int size, int enlarge) {\n    // Initialize an array with extended length\n    int *res = new int[size + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < size; i++) {\n        res[i] = nums[i];\n    }\n    // Free memory\n    delete[] nums;\n    // Return the extended new array\n    return res;\n}\n
array.java
/* Extend array length */\nint[] extend(int[] nums, int enlarge) {\n    // Initialize an array with extended length\n    int[] res = new int[nums.length + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the extended new array\n    return res;\n}\n
array.cs
/* Extend array length */\nint[] Extend(int[] nums, int enlarge) {\n    // Initialize an array with extended length\n    int[] res = new int[nums.Length + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < nums.Length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the extended new array\n    return res;\n}\n
array.go
/* Extend array length */\nfunc extend(nums []int, enlarge int) []int {\n    // Initialize an array with extended length\n    res := make([]int, len(nums)+enlarge)\n    // Copy all elements from the original array to the new array\n    for i, num := range nums {\n        res[i] = num\n    }\n    // Return the extended new array\n    return res\n}\n
array.swift
/* Extend array length */\nfunc extend(nums: [Int], enlarge: Int) -> [Int] {\n    // Initialize an array with extended length\n    var res = Array(repeating: 0, count: nums.count + enlarge)\n    // Copy all elements from the original array to the new array\n    for i in nums.indices {\n        res[i] = nums[i]\n    }\n    // Return the extended new array\n    return res\n}\n
array.js
/* Extend array length */\n// Note: JavaScript's Array is dynamic array, can be directly expanded\n// For learning purposes, this function treats Array as fixed-length array\nfunction extend(nums, enlarge) {\n    // Initialize an array with extended length\n    const res = new Array(nums.length + enlarge).fill(0);\n    // Copy all elements from the original array to the new array\n    for (let i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the extended new array\n    return res;\n}\n
array.ts
/* Extend array length */\n// Note: TypeScript's Array is dynamic array, can be directly expanded\n// For learning purposes, this function treats Array as fixed-length array\nfunction extend(nums: number[], enlarge: number): number[] {\n    // Initialize an array with extended length\n    const res = new Array(nums.length + enlarge).fill(0);\n    // Copy all elements from the original array to the new array\n    for (let i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the extended new array\n    return res;\n}\n
array.dart
/* Extend array length */\nList<int> extend(List<int> nums, int enlarge) {\n  // Initialize an array with extended length\n  List<int> res = List.filled(nums.length + enlarge, 0);\n  // Copy all elements from the original array to the new array\n  for (var i = 0; i < nums.length; i++) {\n    res[i] = nums[i];\n  }\n  // Return the extended new array\n  return res;\n}\n
array.rs
/* Extend array length */\nfn extend(nums: &[i32], enlarge: usize) -> Vec<i32> {\n    // Initialize an array with extended length\n    let mut res: Vec<i32> = vec![0; nums.len() + enlarge];\n    // Copy all elements from original array to new\n    res[0..nums.len()].copy_from_slice(nums);\n\n    // Return the extended new array\n    res\n}\n
array.c
/* Extend array length */\nint *extend(int *nums, int size, int enlarge) {\n    // Initialize an array with extended length\n    int *res = (int *)malloc(sizeof(int) * (size + enlarge));\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < size; i++) {\n        res[i] = nums[i];\n    }\n    // Initialize expanded space\n    for (int i = size; i < size + enlarge; i++) {\n        res[i] = 0;\n    }\n    // Return the extended new array\n    return res;\n}\n
array.kt
/* Extend array length */\nfun extend(nums: IntArray, enlarge: Int): IntArray {\n    // Initialize an array with extended length\n    val res = IntArray(nums.size + enlarge)\n    // Copy all elements from the original array to the new array\n    for (i in nums.indices) {\n        res[i] = nums[i]\n    }\n    // Return the extended new array\n    return res\n}\n
array.rb
### Extend array length ###\n# Note: Ruby's Array is dynamic array, can be directly expanded\n# For learning purposes, this function treats Array as fixed-length array\ndef extend(nums, enlarge)\n  # Initialize an array with extended length\n  res = Array.new(nums.length + enlarge, 0)\n\n  # Copy all elements from the original array to the new array\n  for i in 0...nums.length\n    res[i] = nums[i]\n  end\n\n  # Return the extended new array\n  res\nend\n
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#412-advantages-and-limitations-of-arrays","level":2,"title":"4.1.2   Advantages and Limitations of Arrays","text":"

Arrays are stored in contiguous memory space with elements of the same type. This approach contains rich prior information that the system can use to optimize the efficiency of data structure operations.

  • High space efficiency: Arrays allocate contiguous memory blocks for data without additional structural overhead.
  • Support for random access: Arrays allow accessing any element in \\(O(1)\\) time.
  • Cache locality: When accessing array elements, the computer not only loads the element but also caches the surrounding data, thereby leveraging the cache to improve the execution speed of subsequent operations.

Contiguous space storage is a double-edged sword with the following limitations:

  • Low insertion and deletion efficiency: When an array has many elements, insertion and deletion operations require shifting a large number of elements.
  • Immutable length: After an array is initialized, its length is fixed. Expanding the array requires copying all data to a new array, which is very costly.
  • Space waste: If the allocated size of an array exceeds what is actually needed, the extra space is wasted.
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#413-typical-applications-of-arrays","level":2,"title":"4.1.3   Typical Applications of Arrays","text":"

Arrays are a fundamental and common data structure, frequently used in various algorithms and for implementing various complex data structures.

  • Random access: If we want to randomly sample some items, we can use an array to store them and generate a random sequence to implement random sampling based on indices.
  • Sorting and searching: Arrays are the most commonly used data structure for sorting and searching algorithms. Quick sort, merge sort, binary search, and others are primarily performed on arrays.
  • Lookup tables: When we need to quickly find an element or its corresponding relationship, we can use an array as a lookup table. For example, if we want to implement a mapping from characters to ASCII codes, we can use the ASCII code value of a character as an index, with the corresponding element stored at that position in the array.
  • Machine learning: Neural networks make extensive use of linear algebra operations between vectors, matrices, and tensors, all of which are constructed in the form of arrays. Arrays are the most commonly used data structure in neural network programming.
  • Data structure implementation: Arrays can be used to implement stacks, queues, hash tables, heaps, graphs, and other data structures. For example, the adjacency matrix representation of a graph is essentially a two-dimensional array.
","path":["Chapter 4. Array and Linked List","4.1   Array"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/","level":1,"title":"4.2   Linked List","text":"

Memory space is a shared resource for all programs. In a complex system runtime environment, available memory space may be scattered throughout the memory. We know that the memory space for storing an array must be contiguous, and when the array is very large, the memory may not be able to provide such a large contiguous space. This is where the flexibility advantage of linked lists becomes apparent.

A linked list is a linear data structure in which each element is a node object, and the nodes are connected through \"references\". A reference records the memory address of the next node, through which the next node can be accessed from the current node.

The design of linked lists allows nodes to be stored scattered throughout the memory, and their memory addresses do not need to be contiguous.

Figure 4-5   Linked list definition and storage method

Observing Figure 4-5, the basic unit of a linked list is a node object. Each node contains two pieces of data: the node's \"value\" and a \"reference\" to the next node.

  • The first node of a linked list is called the \"head node\", and the last node is called the \"tail node\".
  • The tail node points to \"null\", which is denoted as null, nullptr, and None in Java, C++, and Python, respectively.
  • In languages that support pointers, such as C, C++, Go, and Rust, the aforementioned \"reference\" should be replaced with \"pointer\".

As shown in the following code, a linked list node ListNode contains not only a value but also an additional reference (pointer). Therefore, linked lists occupy more memory space than arrays when storing the same amount of data.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
class ListNode:\n    \"\"\"Linked list node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val               # Node value\n        self.next: ListNode | None = None # Reference to the next node\n
/* Linked list node structure */\nstruct ListNode {\n    int val;         // Node value\n    ListNode *next;  // Pointer to the next node\n    ListNode(int x) : val(x), next(nullptr) {}  // Constructor\n};\n
/* Linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode next;  // Reference to the next node\n    ListNode(int x) { val = x; }  // Constructor\n}\n
/* Linked list node class */\nclass ListNode(int x) {  // Constructor\n    int val = x;         // Node value\n    ListNode? next;      // Reference to the next node\n}\n
/* Linked list node structure */\ntype ListNode struct {\n    Val  int       // Node value\n    Next *ListNode // Pointer to the next node\n}\n\n// NewListNode Constructor, creates a new linked list\nfunc NewListNode(val int) *ListNode {\n    return &ListNode{\n        Val:  val,\n        Next: nil,\n    }\n}\n
/* Linked list node class */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Reference to the next node\n\n    init(x: Int) { // Constructor\n        val = x\n    }\n}\n
/* Linked list node class */\nclass ListNode {\n    constructor(val, next) {\n        this.val = (val === undefined ? 0 : val);       // Node value\n        this.next = (next === undefined ? null : next); // Reference to the next node\n    }\n}\n
/* Linked list node class */\nclass ListNode {\n    val: number;\n    next: ListNode | null;\n    constructor(val?: number, next?: ListNode | null) {\n        this.val = val === undefined ? 0 : val;        // Node value\n        this.next = next === undefined ? null : next;  // Reference to the next node\n    }\n}\n
/* Linked list node class */\nclass ListNode {\n  int val; // Node value\n  ListNode? next; // Reference to the next node\n  ListNode(this.val, [this.next]); // Constructor\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n/* Linked list node class */\n#[derive(Debug)]\nstruct ListNode {\n    val: i32, // Node value\n    next: Option<Rc<RefCell<ListNode>>>, // Pointer to the next node\n}\n
/* Linked list node structure */\ntypedef struct ListNode {\n    int val;               // Node value\n    struct ListNode *next; // Pointer to the next node\n} ListNode;\n\n/* Constructor */\nListNode *newListNode(int val) {\n    ListNode *node;\n    node = (ListNode *) malloc(sizeof(ListNode));\n    node->val = val;\n    node->next = NULL;\n    return node;\n}\n
/* Linked list node class */\n// Constructor\nclass ListNode(x: Int) {\n    val _val: Int = x          // Node value\n    val next: ListNode? = null // Reference to the next node\n}\n
# Linked list node class\nclass ListNode\n  attr_accessor :val  # Node value\n  attr_accessor :next # Reference to the next node\n\n  def initialize(val=0, next_node=nil)\n    @val = val\n    @next = next_node\n  end\nend\n
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#421-common-linked-list-operations","level":2,"title":"4.2.1   Common Linked List Operations","text":"","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#1-initializing-a-linked-list","level":3,"title":"1.   Initializing a Linked List","text":"

Building a linked list involves two steps: first, initializing each node object; second, constructing the reference relationships between nodes. Once initialization is complete, we can traverse all nodes starting from the head node of the linked list through the reference next.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
# Initialize linked list 1 -> 3 -> 2 -> 5 -> 4\n# Initialize each node\nn0 = ListNode(1)\nn1 = ListNode(3)\nn2 = ListNode(2)\nn3 = ListNode(5)\nn4 = ListNode(4)\n# Build references between nodes\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
linked_list.cpp
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nListNode* n0 = new ListNode(1);\nListNode* n1 = new ListNode(3);\nListNode* n2 = new ListNode(2);\nListNode* n3 = new ListNode(5);\nListNode* n4 = new ListNode(4);\n// Build references between nodes\nn0->next = n1;\nn1->next = n2;\nn2->next = n3;\nn3->next = n4;\n
linked_list.java
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nListNode n0 = new ListNode(1);\nListNode n1 = new ListNode(3);\nListNode n2 = new ListNode(2);\nListNode n3 = new ListNode(5);\nListNode n4 = new ListNode(4);\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.cs
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nListNode n0 = new(1);\nListNode n1 = new(3);\nListNode n2 = new(2);\nListNode n3 = new(5);\nListNode n4 = new(4);\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.go
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nn0 := NewListNode(1)\nn1 := NewListNode(3)\nn2 := NewListNode(2)\nn3 := NewListNode(5)\nn4 := NewListNode(4)\n// Build references between nodes\nn0.Next = n1\nn1.Next = n2\nn2.Next = n3\nn3.Next = n4\n
linked_list.swift
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nlet n0 = ListNode(x: 1)\nlet n1 = ListNode(x: 3)\nlet n2 = ListNode(x: 2)\nlet n3 = ListNode(x: 5)\nlet n4 = ListNode(x: 4)\n// Build references between nodes\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
linked_list.js
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.ts
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.dart
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\\\n// Initialize each node\nListNode n0 = ListNode(1);\nListNode n1 = ListNode(3);\nListNode n2 = ListNode(2);\nListNode n3 = ListNode(5);\nListNode n4 = ListNode(4);\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.rs
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nlet n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None }));\nlet n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None }));\nlet n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None }));\nlet n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None }));\nlet n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None }));\n\n// Build references between nodes\nn0.borrow_mut().next = Some(n1.clone());\nn1.borrow_mut().next = Some(n2.clone());\nn2.borrow_mut().next = Some(n3.clone());\nn3.borrow_mut().next = Some(n4.clone());\n
linked_list.c
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nListNode* n0 = newListNode(1);\nListNode* n1 = newListNode(3);\nListNode* n2 = newListNode(2);\nListNode* n3 = newListNode(5);\nListNode* n4 = newListNode(4);\n// Build references between nodes\nn0->next = n1;\nn1->next = n2;\nn2->next = n3;\nn3->next = n4;\n
linked_list.kt
/* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize each node\nval n0 = ListNode(1)\nval n1 = ListNode(3)\nval n2 = ListNode(2)\nval n3 = ListNode(5)\nval n4 = ListNode(4)\n// Build references between nodes\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
linked_list.rb
# Initialize linked list 1 -> 3 -> 2 -> 5 -> 4\n# Initialize each node\nn0 = ListNode.new(1)\nn1 = ListNode.new(3)\nn2 = ListNode.new(2)\nn3 = ListNode.new(5)\nn4 = ListNode.new(4)\n# Build references between nodes\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
Code Visualization

Full Screen >

An array is a single variable; for example, an array nums contains elements nums[0], nums[1], etc. A linked list, however, is composed of multiple independent node objects. We typically use the head node as the reference to the linked list; for example, the linked list in the above code can be referred to as linked list n0.

","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#2-inserting-a-node","level":3,"title":"2.   Inserting a Node","text":"

Inserting a node in a linked list is very easy. As shown in Figure 4-6, suppose we want to insert a new node P between two adjacent nodes n0 and n1. We only need to change two node references (pointers), with a time complexity of \\(O(1)\\).

In contrast, the time complexity of inserting an element in an array is \\(O(n)\\), which is inefficient when dealing with large amounts of data.

Figure 4-6   Example of inserting a node into a linked list

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
def insert(n0: ListNode, P: ListNode):\n    \"\"\"Insert node P after node n0 in the linked list\"\"\"\n    n1 = n0.next\n    P.next = n1\n    n0.next = P\n
linked_list.cpp
/* Insert node P after node n0 in the linked list */\nvoid insert(ListNode *n0, ListNode *P) {\n    ListNode *n1 = n0->next;\n    P->next = n1;\n    n0->next = P;\n}\n
linked_list.java
/* Insert node P after node n0 in the linked list */\nvoid insert(ListNode n0, ListNode P) {\n    ListNode n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
linked_list.cs
/* Insert node P after node n0 in the linked list */\nvoid Insert(ListNode n0, ListNode P) {\n    ListNode? n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
linked_list.go
/* Insert node P after node n0 in the linked list */\nfunc insertNode(n0 *ListNode, P *ListNode) {\n    n1 := n0.Next\n    P.Next = n1\n    n0.Next = P\n}\n
linked_list.swift
/* Insert node P after node n0 in the linked list */\nfunc insert(n0: ListNode, P: ListNode) {\n    let n1 = n0.next\n    P.next = n1\n    n0.next = P\n}\n
linked_list.js
/* Insert node P after node n0 in the linked list */\nfunction insert(n0, P) {\n    const n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
linked_list.ts
/* Insert node P after node n0 in the linked list */\nfunction insert(n0: ListNode, P: ListNode): void {\n    const n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
linked_list.dart
/* Insert node P after node n0 in the linked list */\nvoid insert(ListNode n0, ListNode P) {\n  ListNode? n1 = n0.next;\n  P.next = n1;\n  n0.next = P;\n}\n
linked_list.rs
/* Insert node P after node n0 in the linked list */\n#[allow(non_snake_case)]\npub fn insert<T>(n0: &Rc<RefCell<ListNode<T>>>, P: Rc<RefCell<ListNode<T>>>) {\n    let n1 = n0.borrow_mut().next.take();\n    P.borrow_mut().next = n1;\n    n0.borrow_mut().next = Some(P);\n}\n
linked_list.c
/* Insert node P after node n0 in the linked list */\nvoid insert(ListNode *n0, ListNode *P) {\n    ListNode *n1 = n0->next;\n    P->next = n1;\n    n0->next = P;\n}\n
linked_list.kt
/* Insert node P after node n0 in the linked list */\nfun insert(n0: ListNode?, p: ListNode?) {\n    val n1 = n0?.next\n    p?.next = n1\n    n0?.next = p\n}\n
linked_list.rb
### Insert node _p after node n0 in linked list ###\n# Ruby's `p` is a built-in function, `P` is a constant, so use `_p` instead\ndef insert(n0, _p)\n  n1 = n0.next\n  _p.next = n1\n  n0.next = _p\nend\n
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#3-removing-a-node","level":3,"title":"3.   Removing a Node","text":"

As shown in Figure 4-7, removing a node in a linked list is also very convenient. We only need to change one node's reference (pointer).

Note that although node P still points to n1 after the deletion operation is complete, the linked list can no longer access P when traversing, which means P no longer belongs to this linked list.

Figure 4-7   Removing a node from a linked list

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
def remove(n0: ListNode):\n    \"\"\"Remove the first node after node n0 in the linked list\"\"\"\n    if not n0.next:\n        return\n    # n0 -> P -> n1\n    P = n0.next\n    n1 = P.next\n    n0.next = n1\n
linked_list.cpp
/* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode *n0) {\n    if (n0->next == nullptr)\n        return;\n    // n0 -> P -> n1\n    ListNode *P = n0->next;\n    ListNode *n1 = P->next;\n    n0->next = n1;\n    // Free memory\n    delete P;\n}\n
linked_list.java
/* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode n0) {\n    if (n0.next == null)\n        return;\n    // n0 -> P -> n1\n    ListNode P = n0.next;\n    ListNode n1 = P.next;\n    n0.next = n1;\n}\n
linked_list.cs
/* Remove the first node after node n0 in the linked list */\nvoid Remove(ListNode n0) {\n    if (n0.next == null)\n        return;\n    // n0 -> P -> n1\n    ListNode P = n0.next;\n    ListNode? n1 = P.next;\n    n0.next = n1;\n}\n
linked_list.go
/* Remove the first node after node n0 in the linked list */\nfunc removeItem(n0 *ListNode) {\n    if n0.Next == nil {\n        return\n    }\n    // n0 -> P -> n1\n    P := n0.Next\n    n1 := P.Next\n    n0.Next = n1\n}\n
linked_list.swift
/* Remove the first node after node n0 in the linked list */\nfunc remove(n0: ListNode) {\n    if n0.next == nil {\n        return\n    }\n    // n0 -> P -> n1\n    let P = n0.next\n    let n1 = P?.next\n    n0.next = n1\n}\n
linked_list.js
/* Remove the first node after node n0 in the linked list */\nfunction remove(n0) {\n    if (!n0.next) return;\n    // n0 -> P -> n1\n    const P = n0.next;\n    const n1 = P.next;\n    n0.next = n1;\n}\n
linked_list.ts
/* Remove the first node after node n0 in the linked list */\nfunction remove(n0: ListNode): void {\n    if (!n0.next) {\n        return;\n    }\n    // n0 -> P -> n1\n    const P = n0.next;\n    const n1 = P.next;\n    n0.next = n1;\n}\n
linked_list.dart
/* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode n0) {\n  if (n0.next == null) return;\n  // n0 -> P -> n1\n  ListNode P = n0.next!;\n  ListNode? n1 = P.next;\n  n0.next = n1;\n}\n
linked_list.rs
/* Remove the first node after node n0 in the linked list */\n#[allow(non_snake_case)]\npub fn remove<T>(n0: &Rc<RefCell<ListNode<T>>>) {\n    // n0 -> P -> n1\n    let P = n0.borrow_mut().next.take();\n    if let Some(node) = P {\n        let n1 = node.borrow_mut().next.take();\n        n0.borrow_mut().next = n1;\n    }\n}\n
linked_list.c
/* Remove the first node after node n0 in the linked list */\n// Note: stdio.h occupies the remove keyword\nvoid removeItem(ListNode *n0) {\n    if (!n0->next)\n        return;\n    // n0 -> P -> n1\n    ListNode *P = n0->next;\n    ListNode *n1 = P->next;\n    n0->next = n1;\n    // Free memory\n    free(P);\n}\n
linked_list.kt
/* Remove the first node after node n0 in the linked list */\nfun remove(n0: ListNode?) {\n    if (n0?.next == null)\n        return\n    // n0 -> P -> n1\n    val p = n0.next\n    val n1 = p?.next\n    n0.next = n1\n}\n
linked_list.rb
### Delete first node after node n0 in linked list ###\ndef remove(n0)\n  return if n0.next.nil?\n\n  # n0 -> remove_node -> n1\n  remove_node = n0.next\n  n1 = remove_node.next\n  n0.next = n1\nend\n
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#4-accessing-a-node","level":3,"title":"4.   Accessing a Node","text":"

Accessing nodes in a linked list is less efficient. As mentioned in the previous section, we can access any element in an array in \\(O(1)\\) time. This is not the case with linked lists. The program needs to start from the head node and traverse backward one by one until the target node is found. That is, accessing the \\(i\\)-th node in a linked list requires \\(i - 1\\) iterations, with a time complexity of \\(O(n)\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
def access(head: ListNode, index: int) -> ListNode | None:\n    \"\"\"Access the node at index index in the linked list\"\"\"\n    for _ in range(index):\n        if not head:\n            return None\n        head = head.next\n    return head\n
linked_list.cpp
/* Access the node at index index in the linked list */\nListNode *access(ListNode *head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == nullptr)\n            return nullptr;\n        head = head->next;\n    }\n    return head;\n}\n
linked_list.java
/* Access the node at index index in the linked list */\nListNode access(ListNode head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == null)\n            return null;\n        head = head.next;\n    }\n    return head;\n}\n
linked_list.cs
/* Access the node at index index in the linked list */\nListNode? Access(ListNode? head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == null)\n            return null;\n        head = head.next;\n    }\n    return head;\n}\n
linked_list.go
/* Access the node at index index in the linked list */\nfunc access(head *ListNode, index int) *ListNode {\n    for i := 0; i < index; i++ {\n        if head == nil {\n            return nil\n        }\n        head = head.Next\n    }\n    return head\n}\n
linked_list.swift
/* Access the node at index index in the linked list */\nfunc access(head: ListNode, index: Int) -> ListNode? {\n    var head: ListNode? = head\n    for _ in 0 ..< index {\n        if head == nil {\n            return nil\n        }\n        head = head?.next\n    }\n    return head\n}\n
linked_list.js
/* Access the node at index index in the linked list */\nfunction access(head, index) {\n    for (let i = 0; i < index; i++) {\n        if (!head) {\n            return null;\n        }\n        head = head.next;\n    }\n    return head;\n}\n
linked_list.ts
/* Access the node at index index in the linked list */\nfunction access(head: ListNode | null, index: number): ListNode | null {\n    for (let i = 0; i < index; i++) {\n        if (!head) {\n            return null;\n        }\n        head = head.next;\n    }\n    return head;\n}\n
linked_list.dart
/* Access the node at index index in the linked list */\nListNode? access(ListNode? head, int index) {\n  for (var i = 0; i < index; i++) {\n    if (head == null) return null;\n    head = head.next;\n  }\n  return head;\n}\n
linked_list.rs
/* Access the node at index index in the linked list */\npub fn access<T>(head: Rc<RefCell<ListNode<T>>>, index: i32) -> Option<Rc<RefCell<ListNode<T>>>> {\n    fn dfs<T>(\n        head: Option<&Rc<RefCell<ListNode<T>>>>,\n        index: i32,\n    ) -> Option<Rc<RefCell<ListNode<T>>>> {\n        if index <= 0 {\n            return head.cloned();\n        }\n\n        if let Some(node) = head {\n            dfs(node.borrow().next.as_ref(), index - 1)\n        } else {\n            None\n        }\n    }\n\n    dfs(Some(head).as_ref(), index)\n}\n
linked_list.c
/* Access the node at index index in the linked list */\nListNode *access(ListNode *head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == NULL)\n            return NULL;\n        head = head->next;\n    }\n    return head;\n}\n
linked_list.kt
/* Access the node at index index in the linked list */\nfun access(head: ListNode?, index: Int): ListNode? {\n    var h = head\n    for (i in 0..<index) {\n        if (h == null)\n            return null\n        h = h.next\n    }\n    return h\n}\n
linked_list.rb
### Access node at index in linked list ###\ndef access(head, index)\n  for i in 0...index\n    return nil if head.nil?\n    head = head.next\n  end\n\n  head\nend\n
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#5-finding-a-node","level":3,"title":"5.   Finding a Node","text":"

Traverse the linked list to find a node with value target, and output the index of that node in the linked list. This process is also a linear search. The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
def find(head: ListNode, target: int) -> int:\n    \"\"\"Find the first node with value target in the linked list\"\"\"\n    index = 0\n    while head:\n        if head.val == target:\n            return index\n        head = head.next\n        index += 1\n    return -1\n
linked_list.cpp
/* Find the first node with value target in the linked list */\nint find(ListNode *head, int target) {\n    int index = 0;\n    while (head != nullptr) {\n        if (head->val == target)\n            return index;\n        head = head->next;\n        index++;\n    }\n    return -1;\n}\n
linked_list.java
/* Find the first node with value target in the linked list */\nint find(ListNode head, int target) {\n    int index = 0;\n    while (head != null) {\n        if (head.val == target)\n            return index;\n        head = head.next;\n        index++;\n    }\n    return -1;\n}\n
linked_list.cs
/* Find the first node with value target in the linked list */\nint Find(ListNode? head, int target) {\n    int index = 0;\n    while (head != null) {\n        if (head.val == target)\n            return index;\n        head = head.next;\n        index++;\n    }\n    return -1;\n}\n
linked_list.go
/* Find the first node with value target in the linked list */\nfunc findNode(head *ListNode, target int) int {\n    index := 0\n    for head != nil {\n        if head.Val == target {\n            return index\n        }\n        head = head.Next\n        index++\n    }\n    return -1\n}\n
linked_list.swift
/* Find the first node with value target in the linked list */\nfunc find(head: ListNode, target: Int) -> Int {\n    var head: ListNode? = head\n    var index = 0\n    while head != nil {\n        if head?.val == target {\n            return index\n        }\n        head = head?.next\n        index += 1\n    }\n    return -1\n}\n
linked_list.js
/* Find the first node with value target in the linked list */\nfunction find(head, target) {\n    let index = 0;\n    while (head !== null) {\n        if (head.val === target) {\n            return index;\n        }\n        head = head.next;\n        index += 1;\n    }\n    return -1;\n}\n
linked_list.ts
/* Find the first node with value target in the linked list */\nfunction find(head: ListNode | null, target: number): number {\n    let index = 0;\n    while (head !== null) {\n        if (head.val === target) {\n            return index;\n        }\n        head = head.next;\n        index += 1;\n    }\n    return -1;\n}\n
linked_list.dart
/* Find the first node with value target in the linked list */\nint find(ListNode? head, int target) {\n  int index = 0;\n  while (head != null) {\n    if (head.val == target) {\n      return index;\n    }\n    head = head.next;\n    index++;\n  }\n  return -1;\n}\n
linked_list.rs
/* Find the first node with value target in the linked list */\npub fn find<T: PartialEq>(head: Rc<RefCell<ListNode<T>>>, target: T) -> i32 {\n    fn find<T: PartialEq>(head: Option<&Rc<RefCell<ListNode<T>>>>, target: T, idx: i32) -> i32 {\n        if let Some(node) = head {\n            if node.borrow().val == target {\n                return idx;\n            }\n            return find(node.borrow().next.as_ref(), target, idx + 1);\n        } else {\n            -1\n        }\n    }\n\n    find(Some(head).as_ref(), target, 0)\n}\n
linked_list.c
/* Find the first node with value target in the linked list */\nint find(ListNode *head, int target) {\n    int index = 0;\n    while (head) {\n        if (head->val == target)\n            return index;\n        head = head->next;\n        index++;\n    }\n    return -1;\n}\n
linked_list.kt
/* Find the first node with value target in the linked list */\nfun find(head: ListNode?, target: Int): Int {\n    var index = 0\n    var h = head\n    while (h != null) {\n        if (h._val == target)\n            return index\n        h = h.next\n        index++\n    }\n    return -1\n}\n
linked_list.rb
### Find first node with value target in linked list ###\ndef find(head, target)\n  index = 0\n  while head\n    return index if head.val == target\n    head = head.next\n    index += 1\n  end\n\n  -1\nend\n
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#422-arrays-vs-linked-lists","level":2,"title":"4.2.2   Arrays vs. Linked Lists","text":"

Table 4-1 summarizes the characteristics of arrays and linked lists and compares their operational efficiencies. Since they employ two opposite storage strategies, their various properties and operational efficiencies also exhibit contrasting characteristics.

Table 4-1   Comparison of array and linked list efficiencies

Array Linked List Storage method Contiguous memory space Scattered memory space Capacity expansion Immutable length Flexible expansion Memory efficiency Elements occupy less memory, but space may be wasted Elements occupy more memory Accessing an element \\(O(1)\\) \\(O(n)\\) Adding an element \\(O(n)\\) \\(O(1)\\) Removing an element \\(O(n)\\) \\(O(1)\\)","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#423-common-types-of-linked-lists","level":2,"title":"4.2.3   Common Types of Linked Lists","text":"

As shown in Figure 4-8, there are three common types of linked lists:

  • Singly linked list: This is the ordinary linked list introduced earlier. The nodes of a singly linked list contain a value and a reference to the next node. We call the first node the head node and the last node the tail node, which points to null None.
  • Circular linked list: If we make the tail node of a singly linked list point to the head node (connecting the tail to the head), we get a circular linked list. In a circular linked list, any node can be viewed as the head node.
  • Doubly linked list: Compared to a singly linked list, a doubly linked list records references in both directions. The node definition of a doubly linked list includes references to both the successor node (next node) and the predecessor node (previous node). Compared to a singly linked list, a doubly linked list is more flexible and can traverse the linked list in both directions, but it also requires more memory space.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
class ListNode:\n    \"\"\"Doubly linked list node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                # Node value\n        self.next: ListNode | None = None  # Reference to the successor node\n        self.prev: ListNode | None = None  # Reference to the predecessor node\n
/* Doubly linked list node structure */\nstruct ListNode {\n    int val;         // Node value\n    ListNode *next;  // Pointer to the successor node\n    ListNode *prev;  // Pointer to the predecessor node\n    ListNode(int x) : val(x), next(nullptr), prev(nullptr) {}  // Constructor\n};\n
/* Doubly linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode next;  // Reference to the successor node\n    ListNode prev;  // Reference to the predecessor node\n    ListNode(int x) { val = x; }  // Constructor\n}\n
/* Doubly linked list node class */\nclass ListNode(int x) {  // Constructor\n    int val = x;    // Node value\n    ListNode next;  // Reference to the successor node\n    ListNode prev;  // Reference to the predecessor node\n}\n
/* Doubly linked list node structure */\ntype DoublyListNode struct {\n    Val  int             // Node value\n    Next *DoublyListNode // Pointer to the successor node\n    Prev *DoublyListNode // Pointer to the predecessor node\n}\n\n// NewDoublyListNode Initialization\nfunc NewDoublyListNode(val int) *DoublyListNode {\n    return &DoublyListNode{\n        Val:  val,\n        Next: nil,\n        Prev: nil,\n    }\n}\n
/* Doubly linked list node class */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Reference to the successor node\n    var prev: ListNode? // Reference to the predecessor node\n\n    init(x: Int) { // Constructor\n        val = x\n    }\n}\n
/* Doubly linked list node class */\nclass ListNode {\n    constructor(val, next, prev) {\n        this.val = val  ===  undefined ? 0 : val;        // Node value\n        this.next = next  ===  undefined ? null : next;  // Reference to the successor node\n        this.prev = prev  ===  undefined ? null : prev;  // Reference to the predecessor node\n    }\n}\n
/* Doubly linked list node class */\nclass ListNode {\n    val: number;\n    next: ListNode | null;\n    prev: ListNode | null;\n    constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {\n        this.val = val  ===  undefined ? 0 : val;        // Node value\n        this.next = next  ===  undefined ? null : next;  // Reference to the successor node\n        this.prev = prev  ===  undefined ? null : prev;  // Reference to the predecessor node\n    }\n}\n
/* Doubly linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode? next;  // Reference to the successor node\n    ListNode? prev;  // Reference to the predecessor node\n    ListNode(this.val, [this.next, this.prev]);  // Constructor\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Doubly linked list node type */\n#[derive(Debug)]\nstruct ListNode {\n    val: i32, // Node value\n    next: Option<Rc<RefCell<ListNode>>>, // Pointer to the successor node\n    prev: Option<Rc<RefCell<ListNode>>>, // Pointer to the predecessor node\n}\n\n/* Constructor */\nimpl ListNode {\n    fn new(val: i32) -> Self {\n        ListNode {\n            val,\n            next: None,\n            prev: None,\n        }\n    }\n}\n
/* Doubly linked list node structure */\ntypedef struct ListNode {\n    int val;               // Node value\n    struct ListNode *next; // Pointer to the successor node\n    struct ListNode *prev; // Pointer to the predecessor node\n} ListNode;\n\n/* Constructor */\nListNode *newListNode(int val) {\n    ListNode *node;\n    node = (ListNode *) malloc(sizeof(ListNode));\n    node->val = val;\n    node->next = NULL;\n    node->prev = NULL;\n    return node;\n}\n
/* Doubly linked list node class */\n// Constructor\nclass ListNode(x: Int) {\n    val _val: Int = x           // Node value\n    val next: ListNode? = null  // Reference to the successor node\n    val prev: ListNode? = null  // Reference to the predecessor node\n}\n
# Doubly linked list node class\nclass ListNode\n  attr_accessor :val    # Node value\n  attr_accessor :next   # Reference to the successor node\n  attr_accessor :prev   # Reference to the predecessor node\n\n  def initialize(val=0, next_node=nil, prev_node=nil)\n    @val = val\n    @next = next_node\n    @prev = prev_node\n  end\nend\n

Figure 4-8   Common types of linked lists

","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#424-typical-applications-of-linked-lists","level":2,"title":"4.2.4   Typical Applications of Linked Lists","text":"

Singly linked lists are commonly used to implement stacks, queues, hash tables, and graphs.

  • Stacks and queues: When insertion and deletion operations both occur at one end of the linked list, it exhibits last-in-first-out characteristics, corresponding to a stack. When insertion operations occur at one end of the linked list and deletion operations occur at the other end, it exhibits first-in-first-out characteristics, corresponding to a queue.
  • Hash tables: Separate chaining is one of the mainstream solutions for resolving hash collisions. In this approach, all colliding elements are placed in a linked list.
  • Graphs: An adjacency list is a common way to represent a graph, where each vertex in the graph is associated with a linked list, and each element in the linked list represents another vertex connected to that vertex.

Doubly linked lists are commonly used in scenarios where quick access to the previous and next elements is needed.

  • Advanced data structures: For example, in red-black trees and B-trees, we need to access the parent node of a node, which can be achieved by saving a reference to the parent node in the node, similar to a doubly linked list.
  • Browser history: In web browsers, when a user clicks the forward or backward button, the browser needs to know the previous and next web pages the user visited. The characteristics of doubly linked lists make this operation simple.
  • LRU algorithm: In cache eviction (LRU) algorithms, we need to quickly find the least recently used data and support quick addition and deletion of nodes. Using a doubly linked list is very suitable for this.

Circular linked lists are commonly used in scenarios that require periodic operations, such as operating system resource scheduling.

  • Round-robin scheduling algorithm: In operating systems, round-robin scheduling is a common CPU scheduling algorithm that needs to cycle through a set of processes. Each process is assigned a time slice, and when the time slice expires, the CPU switches to the next process. This cyclic operation can be implemented using a circular linked list.
  • Data buffers: In some data buffer implementations, circular linked lists may also be used. For example, in audio and video players, the data stream may be divided into multiple buffer blocks and placed in a circular linked list to achieve seamless playback.
","path":["Chapter 4. Array and Linked List","4.2   Linked List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/","level":1,"title":"4.3   List","text":"

A list is an abstract data structure concept that represents an ordered collection of elements, supporting operations such as element access, modification, insertion, deletion, and traversal, without requiring users to consider capacity limitations. Lists can be implemented based on linked lists or arrays.

  • A linked list can naturally be viewed as a list, supporting element insertion, deletion, search, and modification operations, and can flexibly expand dynamically.
  • An array also supports element insertion, deletion, search, and modification, but since its length is immutable, it can only be viewed as a list with length limitations.

When implementing lists using arrays, the immutable length property reduces the practicality of the list. This is because we usually cannot determine in advance how much data we need to store, making it difficult to choose an appropriate list length. If the length is too small, it may fail to meet usage requirements; if the length is too large, it will waste memory space.

To solve this problem, we can use a dynamic array to implement a list. It inherits all the advantages of arrays and can dynamically expand during program execution.

In fact, the lists provided in the standard libraries of many programming languages are implemented based on dynamic arrays, such as list in Python, ArrayList in Java, vector in C++, and List in C#. In the following discussion, we will treat \"list\" and \"dynamic array\" as equivalent concepts.

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#431-common-list-operations","level":2,"title":"4.3.1   Common List Operations","text":"","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#1-initialize-a-list","level":3,"title":"1.   Initialize a List","text":"

We typically use two initialization methods: \"without initial values\" and \"with initial values\":

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Initialize a list\n# Without initial values\nnums1: list[int] = []\n# With initial values\nnums: list[int] = [1, 3, 2, 5, 4]\n
list.cpp
/* Initialize a list */\n// Note that vector in C++ is equivalent to nums as described in this article\n// Without initial values\nvector<int> nums1;\n// With initial values\nvector<int> nums = { 1, 3, 2, 5, 4 };\n
list.java
/* Initialize a list */\n// Without initial values\nList<Integer> nums1 = new ArrayList<>();\n// With initial values (note that array elements should use the wrapper class Integer[] instead of int[])\nInteger[] numbers = new Integer[] { 1, 3, 2, 5, 4 };\nList<Integer> nums = new ArrayList<>(Arrays.asList(numbers));\n
list.cs
/* Initialize a list */\n// Without initial values\nList<int> nums1 = [];\n// With initial values\nint[] numbers = [1, 3, 2, 5, 4];\nList<int> nums = [.. numbers];\n
list_test.go
/* Initialize a list */\n// Without initial values\nnums1 := []int{}\n// With initial values\nnums := []int{1, 3, 2, 5, 4}\n
list.swift
/* Initialize a list */\n// Without initial values\nlet nums1: [Int] = []\n// With initial values\nvar nums = [1, 3, 2, 5, 4]\n
list.js
/* Initialize a list */\n// Without initial values\nconst nums1 = [];\n// With initial values\nconst nums = [1, 3, 2, 5, 4];\n
list.ts
/* Initialize a list */\n// Without initial values\nconst nums1: number[] = [];\n// With initial values\nconst nums: number[] = [1, 3, 2, 5, 4];\n
list.dart
/* Initialize a list */\n// Without initial values\nList<int> nums1 = [];\n// With initial values\nList<int> nums = [1, 3, 2, 5, 4];\n
list.rs
/* Initialize a list */\n// Without initial values\nlet nums1: Vec<i32> = Vec::new();\n// With initial values\nlet nums: Vec<i32> = vec![1, 3, 2, 5, 4];\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Initialize a list */\n// Without initial values\nvar nums1 = listOf<Int>()\n// With initial values\nvar numbers = arrayOf(1, 3, 2, 5, 4)\nvar nums = numbers.toMutableList()\n
list.rb
# Initialize a list\n# Without initial values\nnums1 = []\n# With initial values\nnums = [1, 3, 2, 5, 4]\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#2-access-elements","level":3,"title":"2.   Access Elements","text":"

Since a list is essentially an array, we can access and update elements in \\(O(1)\\) time complexity, which is very efficient.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Access an element\nnum: int = nums[1]  # Access element at index 1\n\n# Update an element\nnums[1] = 0    # Update element at index 1 to 0\n
list.cpp
/* Access an element */\nint num = nums[1];  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Update element at index 1 to 0\n
list.java
/* Access an element */\nint num = nums.get(1);  // Access element at index 1\n\n/* Update an element */\nnums.set(1, 0);  // Update element at index 1 to 0\n
list.cs
/* Access an element */\nint num = nums[1];  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Update element at index 1 to 0\n
list_test.go
/* Access an element */\nnum := nums[1]  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0     // Update element at index 1 to 0\n
list.swift
/* Access an element */\nlet num = nums[1] // Access element at index 1\n\n/* Update an element */\nnums[1] = 0 // Update element at index 1 to 0\n
list.js
/* Access an element */\nconst num = nums[1];  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Update element at index 1 to 0\n
list.ts
/* Access an element */\nconst num: number = nums[1];  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Update element at index 1 to 0\n
list.dart
/* Access an element */\nint num = nums[1];  // Access element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Update element at index 1 to 0\n
list.rs
/* Access an element */\nlet num: i32 = nums[1];  // Access element at index 1\n/* Update an element */\nnums[1] = 0;             // Update element at index 1 to 0\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Access an element */\nval num = nums[1]       // Access element at index 1\n/* Update an element */\nnums[1] = 0             // Update element at index 1 to 0\n
list.rb
# Access an element\nnum = nums[1] # Access element at index 1\n# Update an element\nnums[1] = 0 # Update element at index 1 to 0\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#3-insert-and-delete-elements","level":3,"title":"3.   Insert and Delete Elements","text":"

Compared to arrays, lists can freely add and delete elements. Adding an element at the end of a list has a time complexity of \\(O(1)\\), but inserting and deleting elements still have the same efficiency as arrays, with a time complexity of \\(O(n)\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Clear the list\nnums.clear()\n\n# Add elements at the end\nnums.append(1)\nnums.append(3)\nnums.append(2)\nnums.append(5)\nnums.append(4)\n\n# Insert an element in the middle\nnums.insert(3, 6)  # Insert number 6 at index 3\n\n# Delete an element\nnums.pop(3)        # Delete element at index 3\n
list.cpp
/* Clear the list */\nnums.clear();\n\n/* Add elements at the end */\nnums.push_back(1);\nnums.push_back(3);\nnums.push_back(2);\nnums.push_back(5);\nnums.push_back(4);\n\n/* Insert an element in the middle */\nnums.insert(nums.begin() + 3, 6);  // Insert number 6 at index 3\n\n/* Delete an element */\nnums.erase(nums.begin() + 3);      // Delete element at index 3\n
list.java
/* Clear the list */\nnums.clear();\n\n/* Add elements at the end */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.add(3, 6);  // Insert number 6 at index 3\n\n/* Delete an element */\nnums.remove(3);  // Delete element at index 3\n
list.cs
/* Clear the list */\nnums.Clear();\n\n/* Add elements at the end */\nnums.Add(1);\nnums.Add(3);\nnums.Add(2);\nnums.Add(5);\nnums.Add(4);\n\n/* Insert an element in the middle */\nnums.Insert(3, 6);  // Insert number 6 at index 3\n\n/* Delete an element */\nnums.RemoveAt(3);  // Delete element at index 3\n
list_test.go
/* Clear the list */\nnums = nil\n\n/* Add elements at the end */\nnums = append(nums, 1)\nnums = append(nums, 3)\nnums = append(nums, 2)\nnums = append(nums, 5)\nnums = append(nums, 4)\n\n/* Insert an element in the middle */\nnums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Insert number 6 at index 3\n\n/* Delete an element */\nnums = append(nums[:3], nums[4:]...) // Delete element at index 3\n
list.swift
/* Clear the list */\nnums.removeAll()\n\n/* Add elements at the end */\nnums.append(1)\nnums.append(3)\nnums.append(2)\nnums.append(5)\nnums.append(4)\n\n/* Insert an element in the middle */\nnums.insert(6, at: 3) // Insert number 6 at index 3\n\n/* Delete an element */\nnums.remove(at: 3) // Delete element at index 3\n
list.js
/* Clear the list */\nnums.length = 0;\n\n/* Add elements at the end */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.splice(3, 0, 6); // Insert number 6 at index 3\n\n/* Delete an element */\nnums.splice(3, 1);  // Delete element at index 3\n
list.ts
/* Clear the list */\nnums.length = 0;\n\n/* Add elements at the end */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.splice(3, 0, 6); // Insert number 6 at index 3\n\n/* Delete an element */\nnums.splice(3, 1);  // Delete element at index 3\n
list.dart
/* Clear the list */\nnums.clear();\n\n/* Add elements at the end */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.insert(3, 6); // Insert number 6 at index 3\n\n/* Delete an element */\nnums.removeAt(3); // Delete element at index 3\n
list.rs
/* Clear the list */\nnums.clear();\n\n/* Add elements at the end */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.insert(3, 6);  // Insert number 6 at index 3\n\n/* Delete an element */\nnums.remove(3);    // Delete element at index 3\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Clear the list */\nnums.clear();\n\n/* Add elements at the end */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.add(3, 6);  // Insert number 6 at index 3\n\n/* Delete an element */\nnums.remove(3);  // Delete element at index 3\n
list.rb
# Clear the list\nnums.clear\n\n# Add elements at the end\nnums << 1\nnums << 3\nnums << 2\nnums << 5\nnums << 4\n\n# Insert an element in the middle\nnums.insert(3, 6) # Insert number 6 at index 3\n\n# Delete an element\nnums.delete_at(3) # Delete element at index 3\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#4-traverse-a-list","level":3,"title":"4.   Traverse a List","text":"

Like arrays, lists can be traversed by index or by directly iterating through elements.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Traverse the list by index\ncount = 0\nfor i in range(len(nums)):\n    count += nums[i]\n\n# Traverse list elements directly\nfor num in nums:\n    count += num\n
list.cpp
/* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.size(); i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (int num : nums) {\n    count += num;\n}\n
list.java
/* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.size(); i++) {\n    count += nums.get(i);\n}\n\n/* Traverse list elements directly */\nfor (int num : nums) {\n    count += num;\n}\n
list.cs
/* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.Count; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nforeach (int num in nums) {\n    count += num;\n}\n
list_test.go
/* Traverse the list by index */\ncount := 0\nfor i := 0; i < len(nums); i++ {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\ncount = 0\nfor _, num := range nums {\n    count += num\n}\n
list.swift
/* Traverse the list by index */\nvar count = 0\nfor i in nums.indices {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\ncount = 0\nfor num in nums {\n    count += num\n}\n
list.js
/* Traverse the list by index */\nlet count = 0;\nfor (let i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (const num of nums) {\n    count += num;\n}\n
list.ts
/* Traverse the list by index */\nlet count = 0;\nfor (let i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (const num of nums) {\n    count += num;\n}\n
list.dart
/* Traverse the list by index */\nint count = 0;\nfor (var i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (var num in nums) {\n    count += num;\n}\n
list.rs
// Traverse the list by index\nlet mut _count = 0;\nfor i in 0..nums.len() {\n    _count += nums[i];\n}\n\n// Traverse list elements directly\n_count = 0;\nfor num in &nums {\n    _count += num;\n}\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Traverse the list by index */\nvar count = 0\nfor (i in nums.indices) {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\nfor (num in nums) {\n    count += num\n}\n
list.rb
# Traverse the list by index\ncount = 0\nfor i in 0...nums.length\n    count += nums[i]\nend\n\n# Traverse list elements directly\ncount = 0\nfor num in nums\n    count += num\nend\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#5-concatenate-lists","level":3,"title":"5.   Concatenate Lists","text":"

Given a new list nums1, we can concatenate it to the end of the original list.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Concatenate two lists\nnums1: list[int] = [6, 8, 7, 10, 9]\nnums += nums1  # Concatenate list nums1 to the end of nums\n
list.cpp
/* Concatenate two lists */\nvector<int> nums1 = { 6, 8, 7, 10, 9 };\n// Concatenate list nums1 to the end of nums\nnums.insert(nums.end(), nums1.begin(), nums1.end());\n
list.java
/* Concatenate two lists */\nList<Integer> nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 }));\nnums.addAll(nums1);  // Concatenate list nums1 to the end of nums\n
list.cs
/* Concatenate two lists */\nList<int> nums1 = [6, 8, 7, 10, 9];\nnums.AddRange(nums1);  // Concatenate list nums1 to the end of nums\n
list_test.go
/* Concatenate two lists */\nnums1 := []int{6, 8, 7, 10, 9}\nnums = append(nums, nums1...)  // Concatenate list nums1 to the end of nums\n
list.swift
/* Concatenate two lists */\nlet nums1 = [6, 8, 7, 10, 9]\nnums.append(contentsOf: nums1) // Concatenate list nums1 to the end of nums\n
list.js
/* Concatenate two lists */\nconst nums1 = [6, 8, 7, 10, 9];\nnums.push(...nums1);  // Concatenate list nums1 to the end of nums\n
list.ts
/* Concatenate two lists */\nconst nums1: number[] = [6, 8, 7, 10, 9];\nnums.push(...nums1);  // Concatenate list nums1 to the end of nums\n
list.dart
/* Concatenate two lists */\nList<int> nums1 = [6, 8, 7, 10, 9];\nnums.addAll(nums1);  // Concatenate list nums1 to the end of nums\n
list.rs
/* Concatenate two lists */\nlet nums1: Vec<i32> = vec![6, 8, 7, 10, 9];\nnums.extend(nums1);\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Concatenate two lists */\nval nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList()\nnums.addAll(nums1)  // Concatenate list nums1 to the end of nums\n
list.rb
# Concatenate two lists\nnums1 = [6, 8, 7, 10, 9]\nnums += nums1\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#6-sort-a-list","level":3,"title":"6.   Sort a List","text":"

After sorting a list, we can use \"binary search\" and \"two-pointer\" algorithms, which are frequently tested in array algorithm problems.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
# Sort a list\nnums.sort()  # After sorting, list elements are arranged from smallest to largest\n
list.cpp
/* Sort a list */\nsort(nums.begin(), nums.end());  // After sorting, list elements are arranged from smallest to largest\n
list.java
/* Sort a list */\nCollections.sort(nums);  // After sorting, list elements are arranged from smallest to largest\n
list.cs
/* Sort a list */\nnums.Sort(); // After sorting, list elements are arranged from smallest to largest\n
list_test.go
/* Sort a list */\nsort.Ints(nums)  // After sorting, list elements are arranged from smallest to largest\n
list.swift
/* Sort a list */\nnums.sort() // After sorting, list elements are arranged from smallest to largest\n
list.js
/* Sort a list */\nnums.sort((a, b) => a - b);  // After sorting, list elements are arranged from smallest to largest\n
list.ts
/* Sort a list */\nnums.sort((a, b) => a - b);  // After sorting, list elements are arranged from smallest to largest\n
list.dart
/* Sort a list */\nnums.sort(); // After sorting, list elements are arranged from smallest to largest\n
list.rs
/* Sort a list */\nnums.sort(); // After sorting, list elements are arranged from smallest to largest\n
list.c
// C does not provide built-in dynamic arrays\n
list.kt
/* Sort a list */\nnums.sort() // After sorting, list elements are arranged from smallest to largest\n
list.rb
# Sort a list\nnums = nums.sort { |a, b| a <=> b } # After sorting, list elements are arranged from smallest to largest\n
Code Visualization

Full Screen >

","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#432-list-implementation","level":2,"title":"4.3.2   List Implementation","text":"

Many programming languages have built-in lists, such as Java, C++, and Python. Their implementations are quite complex, and the parameters are carefully considered, such as initial capacity, expansion multiples, and so on. Interested readers can consult the source code to learn more.

To deepen our understanding of how lists work, we attempt to implement a simple list with three key design considerations:

  • Initial capacity: Select a reasonable initial capacity for the underlying array. In this example, we choose 10 as the initial capacity.
  • Size tracking: Declare a variable size to record the current number of elements in the list and update it in real-time as elements are inserted and deleted. Based on this variable, we can locate the end of the list and determine whether expansion is needed.
  • Expansion mechanism: When the list capacity is full upon inserting an element, we need to expand. We create a larger array based on the expansion multiple and then move all elements from the current array to the new array in order. In this example, we specify that the array should be expanded to 2 times its previous size each time.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_list.py
class MyList:\n    \"\"\"List class\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._capacity: int = 10  # List capacity\n        self._arr: list[int] = [0] * self._capacity  # Array (stores list elements)\n        self._size: int = 0  # List length (current number of elements)\n        self._extend_ratio: int = 2  # Multiple by which the list capacity is extended each time\n\n    def size(self) -> int:\n        \"\"\"Get list length (current number of elements)\"\"\"\n        return self._size\n\n    def capacity(self) -> int:\n        \"\"\"Get list capacity\"\"\"\n        return self._capacity\n\n    def get(self, index: int) -> int:\n        \"\"\"Access element\"\"\"\n        # If the index is out of bounds, throw an exception, as below\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        return self._arr[index]\n\n    def set(self, num: int, index: int):\n        \"\"\"Update element\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        self._arr[index] = num\n\n    def add(self, num: int):\n        \"\"\"Add element at the end\"\"\"\n        # When the number of elements exceeds capacity, trigger the extension mechanism\n        if self.size() == self.capacity():\n            self.extend_capacity()\n        self._arr[self._size] = num\n        self._size += 1\n\n    def insert(self, num: int, index: int):\n        \"\"\"Insert element in the middle\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        # When the number of elements exceeds capacity, trigger the extension mechanism\n        if self._size == self.capacity():\n            self.extend_capacity()\n        # Move all elements at and after index index backward by one position\n        for j in range(self._size - 1, index - 1, -1):\n            self._arr[j + 1] = self._arr[j]\n        self._arr[index] = num\n        # Update the number of elements\n        self._size += 1\n\n    def remove(self, index: int) -> int:\n        \"\"\"Remove element\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        num = self._arr[index]\n        # Move all elements after index index forward by one position\n        for j in range(index, self._size - 1):\n            self._arr[j] = self._arr[j + 1]\n        # Update the number of elements\n        self._size -= 1\n        # Return the removed element\n        return num\n\n    def extend_capacity(self):\n        \"\"\"Extend list capacity\"\"\"\n        # Create a new array with length _extend_ratio times the original array, and copy the original array to the new array\n        self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1)\n        # Update list capacity\n        self._capacity = len(self._arr)\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return list with valid length\"\"\"\n        return self._arr[: self._size]\n
my_list.cpp
/* List class */\nclass MyList {\n  private:\n    int *arr;             // Array (stores list elements)\n    int arrCapacity = 10; // List capacity\n    int arrSize = 0;      // List length (current number of elements)\n    int extendRatio = 2;   // Multiple by which the list capacity is extended each time\n\n  public:\n    /* Constructor */\n    MyList() {\n        arr = new int[arrCapacity];\n    }\n\n    /* Destructor */\n    ~MyList() {\n        delete[] arr;\n    }\n\n    /* Get list length (current number of elements)*/\n    int size() {\n        return arrSize;\n    }\n\n    /* Get list capacity */\n    int capacity() {\n        return arrCapacity;\n    }\n\n    /* Update element */\n    int get(int index) {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Add elements at the end */\n    void set(int index, int num) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    void add(int num) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size() == capacity())\n            extendCapacity();\n        arr[size()] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Sort list */\n    void insert(int index, int num) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size() == capacity())\n            extendCapacity();\n        // Move all elements after index index forward by one position\n        for (int j = size() - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Remove element */\n    int remove(int index) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        int num = arr[index];\n        // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array\n        for (int j = index; j < size() - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        arrSize--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    void extendCapacity() {\n        // Create a new array with length extendRatio times the original array\n        int newCapacity = capacity() * extendRatio;\n        int *tmp = arr;\n        arr = new int[newCapacity];\n        // Copy all elements from the original array to the new array\n        for (int i = 0; i < size(); i++) {\n            arr[i] = tmp[i];\n        }\n        // Free memory\n        delete[] tmp;\n        arrCapacity = newCapacity;\n    }\n\n    /* Convert list to Vector for printing */\n    vector<int> toVector() {\n        // Elements enqueue\n        vector<int> vec(size());\n        for (int i = 0; i < size(); i++) {\n            vec[i] = arr[i];\n        }\n        return vec;\n    }\n};\n
my_list.java
/* List class */\nclass MyList {\n    private int[] arr; // Array (stores list elements)\n    private int capacity = 10; // List capacity\n    private int size = 0; // List length (current number of elements)\n    private int extendRatio = 2; // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    public MyList() {\n        arr = new int[capacity];\n    }\n\n    /* Get list length (current number of elements) */\n    public int size() {\n        return size;\n    }\n\n    /* Get list capacity */\n    public int capacity() {\n        return capacity;\n    }\n\n    /* Update element */\n    public int get(int index) {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Add elements at the end */\n    public void set(int index, int num) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    public void add(int num) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size == capacity())\n            extendCapacity();\n        arr[size] = num;\n        // Update the number of elements\n        size++;\n    }\n\n    /* Sort list */\n    public void insert(int index, int num) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size == capacity())\n            extendCapacity();\n        // Move all elements after index index forward by one position\n        for (int j = size - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        size++;\n    }\n\n    /* Remove element */\n    public int remove(int index) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        int num = arr[index];\n        // Move all elements after index forward by one position\n        for (int j = index; j < size - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    public void extendCapacity() {\n        // Create a new array with length extendRatio times the original array and copy the original array to the new array\n        arr = Arrays.copyOf(arr, capacity() * extendRatio);\n        // Add elements at the end\n        capacity = arr.length;\n    }\n\n    /* Convert list to array */\n    public int[] toArray() {\n        int size = size();\n        // Elements enqueue\n        int[] arr = new int[size];\n        for (int i = 0; i < size; i++) {\n            arr[i] = get(i);\n        }\n        return arr;\n    }\n}\n
my_list.cs
/* List class */\nclass MyList {\n    private int[] arr;           // Array (stores list elements)\n    private int arrCapacity = 10;    // List capacity\n    private int arrSize = 0;         // List length (current number of elements)\n    private readonly int extendRatio = 2;  // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    public MyList() {\n        arr = new int[arrCapacity];\n    }\n\n    /* Get list length (current number of elements) */\n    public int Size() {\n        return arrSize;\n    }\n\n    /* Get list capacity */\n    public int Capacity() {\n        return arrCapacity;\n    }\n\n    /* Update element */\n    public int Get(int index) {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Add elements at the end */\n    public void Set(int index, int num) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    public void Add(int num) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (arrSize == arrCapacity)\n            ExtendCapacity();\n        arr[arrSize] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Sort list */\n    public void Insert(int index, int num) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (arrSize == arrCapacity)\n            ExtendCapacity();\n        // Move all elements after index index forward by one position\n        for (int j = arrSize - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Remove element */\n    public int Remove(int index) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        int num = arr[index];\n        // Move all elements after index forward by one position\n        for (int j = index; j < arrSize - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        arrSize--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    public void ExtendCapacity() {\n        // Create new array of length arrCapacity * extendRatio and copy original array to new array\n        Array.Resize(ref arr, arrCapacity * extendRatio);\n        // Add elements at the end\n        arrCapacity = arr.Length;\n    }\n\n    /* Convert list to array */\n    public int[] ToArray() {\n        // Elements enqueue\n        int[] arr = new int[arrSize];\n        for (int i = 0; i < arrSize; i++) {\n            arr[i] = Get(i);\n        }\n        return arr;\n    }\n}\n
my_list.go
/* List class */\ntype myList struct {\n    arrCapacity int\n    arr         []int\n    arrSize     int\n    extendRatio int\n}\n\n/* Constructor */\nfunc newMyList() *myList {\n    return &myList{\n        arrCapacity: 10,              // List capacity\n        arr:         make([]int, 10), // Array (stores list elements)\n        arrSize:     0,               // List length (current number of elements)\n        extendRatio: 2,               // Multiple by which the list capacity is extended each time\n    }\n}\n\n/* Get list length (current number of elements) */\nfunc (l *myList) size() int {\n    return l.arrSize\n}\n\n/* Get list capacity */\nfunc (l *myList) capacity() int {\n    return l.arrCapacity\n}\n\n/* Update element */\nfunc (l *myList) get(index int) int {\n    // If the index is out of bounds, throw an exception, as below\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    return l.arr[index]\n}\n\n/* Add elements at the end */\nfunc (l *myList) set(num, index int) {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    l.arr[index] = num\n}\n\n/* Direct traversal of list elements */\nfunc (l *myList) add(num int) {\n    // When the number of elements exceeds capacity, trigger the extension mechanism\n    if l.arrSize == l.arrCapacity {\n        l.extendCapacity()\n    }\n    l.arr[l.arrSize] = num\n    // Update the number of elements\n    l.arrSize++\n}\n\n/* Sort list */\nfunc (l *myList) insert(num, index int) {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    // When the number of elements exceeds capacity, trigger the extension mechanism\n    if l.arrSize == l.arrCapacity {\n        l.extendCapacity()\n    }\n    // Move all elements after index index forward by one position\n    for j := l.arrSize - 1; j >= index; j-- {\n        l.arr[j+1] = l.arr[j]\n    }\n    l.arr[index] = num\n    // Update the number of elements\n    l.arrSize++\n}\n\n/* Remove element */\nfunc (l *myList) remove(index int) int {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    num := l.arr[index]\n    // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array\n    for j := index; j < l.arrSize-1; j++ {\n        l.arr[j] = l.arr[j+1]\n    }\n    // Update the number of elements\n    l.arrSize--\n    // Return the removed element\n    return num\n}\n\n/* Driver Code */\nfunc (l *myList) extendCapacity() {\n    // Create a new array with length extendRatio times the original array and copy the original array to the new array\n    l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...)\n    // Add elements at the end\n    l.arrCapacity = len(l.arr)\n}\n\n/* Return list with valid length */\nfunc (l *myList) toArray() []int {\n    // Elements enqueue\n    return l.arr[:l.arrSize]\n}\n
my_list.swift
/* List class */\nclass MyList {\n    private var arr: [Int] // Array (stores list elements)\n    private var _capacity: Int // List capacity\n    private var _size: Int // List length (current number of elements)\n    private let extendRatio: Int // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    init() {\n        _capacity = 10\n        _size = 0\n        extendRatio = 2\n        arr = Array(repeating: 0, count: _capacity)\n    }\n\n    /* Get list length (current number of elements) */\n    func size() -> Int {\n        _size\n    }\n\n    /* Get list capacity */\n    func capacity() -> Int {\n        _capacity\n    }\n\n    /* Update element */\n    func get(index: Int) -> Int {\n        // Throw error if index out of bounds, same below\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        return arr[index]\n    }\n\n    /* Add elements at the end */\n    func set(index: Int, num: Int) {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        arr[index] = num\n    }\n\n    /* Direct traversal of list elements */\n    func add(num: Int) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if size() == capacity() {\n            extendCapacity()\n        }\n        arr[size()] = num\n        // Update the number of elements\n        _size += 1\n    }\n\n    /* Sort list */\n    func insert(index: Int, num: Int) {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if size() == capacity() {\n            extendCapacity()\n        }\n        // Move all elements after index index forward by one position\n        for j in (index ..< size()).reversed() {\n            arr[j + 1] = arr[j]\n        }\n        arr[index] = num\n        // Update the number of elements\n        _size += 1\n    }\n\n    /* Remove element */\n    @discardableResult\n    func remove(index: Int) -> Int {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        let num = arr[index]\n        // Move all elements after index forward by one position\n        for j in index ..< (size() - 1) {\n            arr[j] = arr[j + 1]\n        }\n        // Update the number of elements\n        _size -= 1\n        // Return the removed element\n        return num\n    }\n\n    /* Driver Code */\n    func extendCapacity() {\n        // Create a new array with length extendRatio times the original array and copy the original array to the new array\n        arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1))\n        // Add elements at the end\n        _capacity = arr.count\n    }\n\n    /* Convert list to array */\n    func toArray() -> [Int] {\n        Array(arr.prefix(size()))\n    }\n}\n
my_list.js
/* List class */\nclass MyList {\n    #arr = new Array(); // Array (stores list elements)\n    #capacity = 10; // List capacity\n    #size = 0; // List length (current number of elements)\n    #extendRatio = 2; // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    constructor() {\n        this.#arr = new Array(this.#capacity);\n    }\n\n    /* Get list length (current number of elements) */\n    size() {\n        return this.#size;\n    }\n\n    /* Get list capacity */\n    capacity() {\n        return this.#capacity;\n    }\n\n    /* Update element */\n    get(index) {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        return this.#arr[index];\n    }\n\n    /* Add elements at the end */\n    set(index, num) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        this.#arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    add(num) {\n        // If length equals capacity, need to expand\n        if (this.#size === this.#capacity) {\n            this.extendCapacity();\n        }\n        // Add new element to end of list\n        this.#arr[this.#size] = num;\n        this.#size++;\n    }\n\n    /* Sort list */\n    insert(index, num) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (this.#size === this.#capacity) {\n            this.extendCapacity();\n        }\n        // Move all elements after index index forward by one position\n        for (let j = this.#size - 1; j >= index; j--) {\n            this.#arr[j + 1] = this.#arr[j];\n        }\n        // Update the number of elements\n        this.#arr[index] = num;\n        this.#size++;\n    }\n\n    /* Remove element */\n    remove(index) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        let num = this.#arr[index];\n        // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array\n        for (let j = index; j < this.#size - 1; j++) {\n            this.#arr[j] = this.#arr[j + 1];\n        }\n        // Update the number of elements\n        this.#size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    extendCapacity() {\n        // Create a new array with length extendRatio times the original array and copy the original array to the new array\n        this.#arr = this.#arr.concat(\n            new Array(this.capacity() * (this.#extendRatio - 1))\n        );\n        // Add elements at the end\n        this.#capacity = this.#arr.length;\n    }\n\n    /* Convert list to array */\n    toArray() {\n        let size = this.size();\n        // Elements enqueue\n        const arr = new Array(size);\n        for (let i = 0; i < size; i++) {\n            arr[i] = this.get(i);\n        }\n        return arr;\n    }\n}\n
my_list.ts
/* List class */\nclass MyList {\n    private arr: Array<number>; // Array (stores list elements)\n    private _capacity: number = 10; // List capacity\n    private _size: number = 0; // List length (current number of elements)\n    private extendRatio: number = 2; // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    constructor() {\n        this.arr = new Array(this._capacity);\n    }\n\n    /* Get list length (current number of elements) */\n    public size(): number {\n        return this._size;\n    }\n\n    /* Get list capacity */\n    public capacity(): number {\n        return this._capacity;\n    }\n\n    /* Update element */\n    public get(index: number): number {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        return this.arr[index];\n    }\n\n    /* Add elements at the end */\n    public set(index: number, num: number): void {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        this.arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    public add(num: number): void {\n        // If length equals capacity, need to expand\n        if (this._size === this._capacity) this.extendCapacity();\n        // Add new element to end of list\n        this.arr[this._size] = num;\n        this._size++;\n    }\n\n    /* Sort list */\n    public insert(index: number, num: number): void {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (this._size === this._capacity) {\n            this.extendCapacity();\n        }\n        // Move all elements after index index forward by one position\n        for (let j = this._size - 1; j >= index; j--) {\n            this.arr[j + 1] = this.arr[j];\n        }\n        // Update the number of elements\n        this.arr[index] = num;\n        this._size++;\n    }\n\n    /* Remove element */\n    public remove(index: number): number {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        let num = this.arr[index];\n        // Move all elements after index forward by one position\n        for (let j = index; j < this._size - 1; j++) {\n            this.arr[j] = this.arr[j + 1];\n        }\n        // Update the number of elements\n        this._size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    public extendCapacity(): void {\n        // Create new array of length size and copy original array to new array\n        this.arr = this.arr.concat(\n            new Array(this.capacity() * (this.extendRatio - 1))\n        );\n        // Add elements at the end\n        this._capacity = this.arr.length;\n    }\n\n    /* Convert list to array */\n    public toArray(): number[] {\n        let size = this.size();\n        // Elements enqueue\n        const arr = new Array(size);\n        for (let i = 0; i < size; i++) {\n            arr[i] = this.get(i);\n        }\n        return arr;\n    }\n}\n
my_list.dart
/* List class */\nclass MyList {\n  late List<int> _arr; // Array (stores list elements)\n  int _capacity = 10; // List capacity\n  int _size = 0; // List length (current number of elements)\n  int _extendRatio = 2; // Multiple by which the list capacity is extended each time\n\n  /* Constructor */\n  MyList() {\n    _arr = List.filled(_capacity, 0);\n  }\n\n  /* Get list length (current number of elements) */\n  int size() => _size;\n\n  /* Get list capacity */\n  int capacity() => _capacity;\n\n  /* Update element */\n  int get(int index) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    return _arr[index];\n  }\n\n  /* Add elements at the end */\n  void set(int index, int _num) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    _arr[index] = _num;\n  }\n\n  /* Direct traversal of list elements */\n  void add(int _num) {\n    // When the number of elements exceeds capacity, trigger the extension mechanism\n    if (_size == _capacity) extendCapacity();\n    _arr[_size] = _num;\n    // Update the number of elements\n    _size++;\n  }\n\n  /* Sort list */\n  void insert(int index, int _num) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    // When the number of elements exceeds capacity, trigger the extension mechanism\n    if (_size == _capacity) extendCapacity();\n    // Move all elements after index index forward by one position\n    for (var j = _size - 1; j >= index; j--) {\n      _arr[j + 1] = _arr[j];\n    }\n    _arr[index] = _num;\n    // Update the number of elements\n    _size++;\n  }\n\n  /* Remove element */\n  int remove(int index) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    int _num = _arr[index];\n    // Move all elements after index forward by one position\n    for (var j = index; j < _size - 1; j++) {\n      _arr[j] = _arr[j + 1];\n    }\n    // Update the number of elements\n    _size--;\n    // Return the removed element\n    return _num;\n  }\n\n  /* Driver Code */\n  void extendCapacity() {\n    // Create new array with length _extendRatio times original array\n    final _newNums = List.filled(_capacity * _extendRatio, 0);\n    // Copy original array to new array\n    List.copyRange(_newNums, 0, _arr);\n    // Update _arr reference\n    _arr = _newNums;\n    // Add elements at the end\n    _capacity = _arr.length;\n  }\n\n  /* Convert list to array */\n  List<int> toArray() {\n    List<int> arr = [];\n    for (var i = 0; i < _size; i++) {\n      arr.add(get(i));\n    }\n    return arr;\n  }\n}\n
my_list.rs
/* List class */\n#[allow(dead_code)]\nstruct MyList {\n    arr: Vec<i32>,       // Array (stores list elements)\n    capacity: usize,     // List capacity\n    size: usize,         // List length (current number of elements)\n    extend_ratio: usize, // Multiple by which the list capacity is extended each time\n}\n\n#[allow(unused, unused_comparisons)]\nimpl MyList {\n    /* Constructor */\n    pub fn new(capacity: usize) -> Self {\n        let mut vec = vec![0; capacity];\n        Self {\n            arr: vec,\n            capacity,\n            size: 0,\n            extend_ratio: 2,\n        }\n    }\n\n    /* Get list length (current number of elements) */\n    pub fn size(&self) -> usize {\n        return self.size;\n    }\n\n    /* Get list capacity */\n    pub fn capacity(&self) -> usize {\n        return self.capacity;\n    }\n\n    /* Update element */\n    pub fn get(&self, index: usize) -> i32 {\n        // If the index is out of bounds, throw an exception, as below\n        if index >= self.size {\n            panic!(\"Index out of bounds\")\n        };\n        return self.arr[index];\n    }\n\n    /* Add elements at the end */\n    pub fn set(&mut self, index: usize, num: i32) {\n        if index >= self.size {\n            panic!(\"Index out of bounds\")\n        };\n        self.arr[index] = num;\n    }\n\n    /* Direct traversal of list elements */\n    pub fn add(&mut self, num: i32) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if self.size == self.capacity() {\n            self.extend_capacity();\n        }\n        self.arr[self.size] = num;\n        // Update the number of elements\n        self.size += 1;\n    }\n\n    /* Sort list */\n    pub fn insert(&mut self, index: usize, num: i32) {\n        if index >= self.size() {\n            panic!(\"Index out of bounds\")\n        };\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if self.size == self.capacity() {\n            self.extend_capacity();\n        }\n        // Move all elements after index index forward by one position\n        for j in (index..self.size).rev() {\n            self.arr[j + 1] = self.arr[j];\n        }\n        self.arr[index] = num;\n        // Update the number of elements\n        self.size += 1;\n    }\n\n    /* Remove element */\n    pub fn remove(&mut self, index: usize) -> i32 {\n        if index >= self.size() {\n            panic!(\"Index out of bounds\")\n        };\n        let num = self.arr[index];\n        // Create a new array with length _extend_ratio times the original array, and copy the original array to the new array\n        for j in index..self.size - 1 {\n            self.arr[j] = self.arr[j + 1];\n        }\n        // Update the number of elements\n        self.size -= 1;\n        // Return the removed element\n        return num;\n    }\n\n    /* Driver Code */\n    pub fn extend_capacity(&mut self) {\n        // Create new array with length extend_ratio times original, copy original array to new array\n        let new_capacity = self.capacity * self.extend_ratio;\n        self.arr.resize(new_capacity, 0);\n        // Add elements at the end\n        self.capacity = new_capacity;\n    }\n\n    /* Convert list to array */\n    pub fn to_array(&self) -> Vec<i32> {\n        // Elements enqueue\n        let mut arr = Vec::new();\n        for i in 0..self.size {\n            arr.push(self.get(i));\n        }\n        arr\n    }\n}\n
my_list.c
/* List class */\ntypedef struct {\n    int *arr;        // Array (stores list elements)\n    int capacity;    // List capacity\n    int size;        // List size\n    int extendRatio; // List expansion multiplier\n} MyList;\n\n/* Constructor */\nMyList *newMyList() {\n    MyList *nums = malloc(sizeof(MyList));\n    nums->capacity = 10;\n    nums->arr = malloc(sizeof(int) * nums->capacity);\n    nums->size = 0;\n    nums->extendRatio = 2;\n    return nums;\n}\n\n/* Destructor */\nvoid delMyList(MyList *nums) {\n    free(nums->arr);\n    free(nums);\n}\n\n/* Get list length */\nint size(MyList *nums) {\n    return nums->size;\n}\n\n/* Get list capacity */\nint capacity(MyList *nums) {\n    return nums->capacity;\n}\n\n/* Update element */\nint get(MyList *nums, int index) {\n    assert(index >= 0 && index < nums->size);\n    return nums->arr[index];\n}\n\n/* Add elements at the end */\nvoid set(MyList *nums, int index, int num) {\n    assert(index >= 0 && index < nums->size);\n    nums->arr[index] = num;\n}\n\n/* Direct traversal of list elements */\nvoid add(MyList *nums, int num) {\n    if (size(nums) == capacity(nums)) {\n        extendCapacity(nums); // Expand capacity\n    }\n    nums->arr[size(nums)] = num;\n    nums->size++;\n}\n\n/* Sort list */\nvoid insert(MyList *nums, int index, int num) {\n    assert(index >= 0 && index < size(nums));\n    // When the number of elements exceeds capacity, trigger the extension mechanism\n    if (size(nums) == capacity(nums)) {\n        extendCapacity(nums); // Expand capacity\n    }\n    for (int i = size(nums); i > index; --i) {\n        nums->arr[i] = nums->arr[i - 1];\n    }\n    nums->arr[index] = num;\n    nums->size++;\n}\n\n/* Remove element */\n// Note: stdio.h occupies the remove keyword\nint removeItem(MyList *nums, int index) {\n    assert(index >= 0 && index < size(nums));\n    int num = nums->arr[index];\n    for (int i = index; i < size(nums) - 1; i++) {\n        nums->arr[i] = nums->arr[i + 1];\n    }\n    nums->size--;\n    return num;\n}\n\n/* Driver Code */\nvoid extendCapacity(MyList *nums) {\n    // Allocate space first\n    int newCapacity = capacity(nums) * nums->extendRatio;\n    int *extend = (int *)malloc(sizeof(int) * newCapacity);\n    int *temp = nums->arr;\n\n    // Copy old data to new data\n    for (int i = 0; i < size(nums); i++)\n        extend[i] = nums->arr[i];\n\n    // Free old data\n    free(temp);\n\n    // Update new data\n    nums->arr = extend;\n    nums->capacity = newCapacity;\n}\n\n/* Convert list to Array for printing */\nint *toArray(MyList *nums) {\n    return nums->arr;\n}\n
my_list.kt
/* List class */\nclass MyList {\n    private var arr: IntArray = intArrayOf() // Array (stores list elements)\n    private var capacity: Int = 10 // List capacity\n    private var size: Int = 0 // List length (current number of elements)\n    private var extendRatio: Int = 2 // Multiple by which the list capacity is extended each time\n\n    /* Constructor */\n    init {\n        arr = IntArray(capacity)\n    }\n\n    /* Get list length (current number of elements) */\n    fun size(): Int {\n        return size\n    }\n\n    /* Get list capacity */\n    fun capacity(): Int {\n        return capacity\n    }\n\n    /* Update element */\n    fun get(index: Int): Int {\n        // If the index is out of bounds, throw an exception, as below\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        return arr[index]\n    }\n\n    /* Add elements at the end */\n    fun set(index: Int, num: Int) {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        arr[index] = num\n    }\n\n    /* Direct traversal of list elements */\n    fun add(num: Int) {\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size == capacity())\n            extendCapacity()\n        arr[size] = num\n        // Update the number of elements\n        size++\n    }\n\n    /* Sort list */\n    fun insert(index: Int, num: Int) {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        // When the number of elements exceeds capacity, trigger the extension mechanism\n        if (size == capacity())\n            extendCapacity()\n        // Move all elements after index index forward by one position\n        for (j in size - 1 downTo index)\n            arr[j + 1] = arr[j]\n        arr[index] = num\n        // Update the number of elements\n        size++\n    }\n\n    /* Remove element */\n    fun remove(index: Int): Int {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        val num = arr[index]\n        // Move all elements after index forward by one position\n        for (j in index..<size - 1)\n            arr[j] = arr[j + 1]\n        // Update the number of elements\n        size--\n        // Return the removed element\n        return num\n    }\n\n    /* Driver Code */\n    fun extendCapacity() {\n        // Create a new array with length extendRatio times the original array and copy the original array to the new array\n        arr = arr.copyOf(capacity() * extendRatio)\n        // Add elements at the end\n        capacity = arr.size\n    }\n\n    /* Convert list to array */\n    fun toArray(): IntArray {\n        val size = size()\n        // Elements enqueue\n        val arr = IntArray(size)\n        for (i in 0..<size) {\n            arr[i] = get(i)\n        }\n        return arr\n    }\n}\n
my_list.rb
### List class ###\nclass MyList\n  attr_reader :size       # Get list length (current number of elements)\n  attr_reader :capacity   # Get list capacity\n\n  ### Constructor ###\n  def initialize\n    @capacity = 10\n    @size = 0\n    @extend_ratio = 2\n    @arr = Array.new(capacity)\n  end\n\n  ### Access element ###\n  def get(index)\n    # If the index is out of bounds, throw an exception, as below\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    @arr[index]\n  end\n\n  ### Access element ###\n  def set(index, num)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    @arr[index] = num\n  end\n\n  ### Add element at end ###\n  def add(num)\n    # When the number of elements exceeds capacity, trigger the extension mechanism\n    extend_capacity if size == capacity\n    @arr[size] = num\n\n    # Update the number of elements\n    @size += 1\n  end\n\n  ### Insert element in middle ###\n  def insert(index, num)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n\n    # When the number of elements exceeds capacity, trigger the extension mechanism\n    extend_capacity if size == capacity\n\n    # Move all elements after index index forward by one position\n    for j in (size - 1).downto(index)\n      @arr[j + 1] = @arr[j]\n    end\n    @arr[index] = num\n\n    # Update the number of elements\n    @size += 1\n  end\n\n  ### Delete element ###\n  def remove(index)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    num = @arr[index]\n\n    # Move all elements after index forward by one position\n    for j in index...size\n      @arr[j] = @arr[j + 1]\n    end\n\n    # Update the number of elements\n    @size -= 1\n\n    # Return the removed element\n    num\n  end\n\n  ### Expand list capacity ###\n  def extend_capacity\n    # Create new array with length extend_ratio times original, copy original array to new array\n    arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1))\n    # Add elements at the end\n    @capacity = arr.length\n  end\n\n  ### Convert list to array ###\n  def to_array\n    sz = size\n    # Elements enqueue\n    arr = Array.new(sz)\n    for i in 0...sz\n      arr[i] = get(i)\n    end\n    arr\n  end\nend\n
","path":["Chapter 4. Array and Linked List","4.3   List"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/","level":1,"title":"4.4   Random-Access Memory and Cache *","text":"

In the first two sections of this chapter, we explored arrays and linked lists, two fundamental and important data structures that represent \"contiguous storage\" and \"distributed storage\" as two physical structures, respectively.

In fact, physical structure largely determines the efficiency with which programs utilize memory and cache, which in turn affects the overall performance of algorithmic programs.

","path":["Chapter 4. Array and Linked List","4.4   Random-Access Memory and Cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#441-computer-storage-devices","level":2,"title":"4.4.1   Computer Storage Devices","text":"

Computers include three types of storage devices: hard disk, random-access memory (RAM), and cache memory. The following table shows their different roles and performance characteristics in a computer system.

Table 4-2   Computer Storage Devices

Hard Disk RAM Cache Purpose Long-term storage of data, including operating systems, programs, and files Temporary storage of currently running programs and data being processed Storage of frequently accessed data and instructions to reduce CPU's accesses to memory Volatility Data is not lost after power-off Data is lost after power-off Data is lost after power-off Capacity Large, on the order of terabytes (TB) Small, on the order of gigabytes (GB) Very small, on the order of megabytes (MB) Speed Slow, hundreds to thousands of MB/s Fast, tens of GB/s Very fast, tens to hundreds of GB/s Cost (USD/GB) Inexpensive, fractions of a dollar to a few dollars per GB Expensive, tens to hundreds of dollars per GB Very expensive, priced as part of the CPU package

We can imagine the computer storage system as a pyramid structure as shown in the diagram below. Storage devices closer to the top of the pyramid are faster, have smaller capacity, and are more expensive. This multi-layered design is not by accident, but rather the result of careful consideration by computer scientists and engineers.

  • Hard disk cannot be easily replaced by RAM. First, data in memory is lost after power-off, making it unsuitable for long-term data storage. Second, memory is tens of times more expensive than hard disk, which makes it difficult to popularize in the consumer market.
  • Cache cannot simultaneously achieve large capacity and high speed. As the capacity of L1, L2, and L3 caches increases, their physical size becomes larger, and the physical distance between them and the CPU core increases, resulting in longer data transmission time and higher element access latency. With current technology, the multi-layered cache structure represents the best balance point between capacity, speed, and cost.

Figure 4-9   Computer Storage System

Tip

The storage hierarchy of computers embodies a delicate balance among speed, capacity, and cost. In fact, such trade-offs are common across all industrial fields, requiring us to find the optimal balance point between different advantages and constraints.

In summary, hard disk is used for long-term storage of large amounts of data, RAM is used for temporary storage of data being processed during program execution, and cache is used for storage of frequently accessed data and instructions, to improve program execution efficiency. The three work together to ensure efficient operation of the computer system.

As shown in the diagram below, during program execution, data is read from the hard disk into RAM for CPU computation. Cache can be viewed as part of the CPU, it intelligently loads data from RAM, providing the CPU with high-speed data reading, thereby significantly improving program execution efficiency and reducing reliance on slower RAM.

Figure 4-10   Data Flow Among Hard Disk, RAM, and Cache

","path":["Chapter 4. Array and Linked List","4.4   Random-Access Memory and Cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#442-memory-efficiency-of-data-structures","level":2,"title":"4.4.2   Memory Efficiency of Data Structures","text":"

In terms of memory space utilization, arrays and linked lists each have advantages and limitations.

On one hand, memory is limited, and the same memory cannot be shared by multiple programs, so we hope data structures can utilize space as efficiently as possible. Array elements are tightly packed and do not require additional space to store references (pointers) between linked list nodes, thus having higher space efficiency. However, arrays need to allocate sufficient contiguous memory space at once, which may lead to memory waste, and array expansion requires additional time and space costs. In comparison, linked lists perform dynamic memory allocation and deallocation on a \"node\" basis, providing greater flexibility.

On the other hand, during program execution, as memory is repeatedly allocated and freed, the degree of fragmentation of free memory becomes increasingly severe, leading to reduced memory utilization efficiency. Arrays, due to their contiguous storage approach, are relatively less prone to memory fragmentation. Conversely, linked list elements are distributed in storage, and frequent insertion and deletion operations are more likely to cause memory fragmentation.

","path":["Chapter 4. Array and Linked List","4.4   Random-Access Memory and Cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#443-cache-efficiency-of-data-structures","level":2,"title":"4.4.3   Cache Efficiency of Data Structures","text":"

Although cache has much smaller space capacity than memory, it is much faster than memory and plays a crucial role in program execution speed. Since cache capacity is limited and can only store a small portion of frequently accessed data, when the CPU attempts to access data that is not in the cache, a cache miss occurs, and the CPU must load the required data from the slower memory.

Clearly, the fewer \"cache misses,\" the higher the efficiency of CPU data reads and writes, and the better the program performance. We call the proportion of data that the CPU successfully obtains from the cache the cache hit rate, a metric typically used to measure cache efficiency.

To achieve the highest efficiency possible, cache employs the following data loading mechanisms.

  • Cache lines: The cache does not store and load data on a byte-by-byte basis, but rather as cache lines. Compared to byte-by-byte transmission, cache line transmission is more efficient.
  • Prefetching mechanism: The processor attempts to predict data access patterns (e.g., sequential access, fixed-stride jumping access, etc.) and loads data into the cache according to specific patterns, thereby improving hit rate.
  • Spatial locality: If a piece of data is accessed, nearby data may also be accessed in the near future. Therefore, when the cache loads a particular piece of data, it also loads nearby data to improve hit rate.
  • Temporal locality: If a piece of data is accessed, it is likely to be accessed again in the near future. Cache leverages this principle by retaining recently accessed data to improve hit rate.

In fact, arrays and linked lists have different efficiencies in utilizing cache, manifested in the following aspects.

  • Space occupied: Linked list elements occupy more space than array elements, resulting in fewer effective data in the cache.
  • Cache lines: Linked list data are scattered throughout memory, while cache loads \"by lines,\" so the proportion of invalid data loaded is higher.
  • Prefetching mechanism: Arrays have more \"predictable\" data access patterns than linked lists, making it easier for the system to guess which data will be loaded next.
  • Spatial locality: Arrays are stored in centralized memory space, so data near loaded data is more likely to be accessed soon.

Overall, arrays have higher cache hit rates, thus they usually outperform linked lists in operation efficiency. This makes data structures implemented based on arrays more popular when solving algorithmic problems.

It is important to note that high cache efficiency does not mean arrays are superior to linked lists in all cases. In practical applications, which data structure to choose should be determined based on specific requirements. For example, both arrays and linked lists can implement the \"stack\" data structure (which will be discussed in detail in the next chapter), but they are suitable for different scenarios.

  • When solving algorithm problems, we tend to prefer stack implementations based on arrays, because they provide higher operation efficiency and the ability of random access, at the cost of needing to pre-allocate a certain amount of memory space for the array.
  • If the data volume is very large, the dynamic nature is high, and the expected size of the stack is difficult to estimate, then a stack implementation based on linked lists is more suitable. Linked lists can distribute large amounts of data across different parts of memory and avoid the additional overhead produced by array expansion.
","path":["Chapter 4. Array and Linked List","4.4   Random-Access Memory and Cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/","level":1,"title":"4.5   Summary","text":"","path":["Chapter 4. Array and Linked List","4.5   Summary"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Arrays and linked lists are two fundamental data structures, representing two different ways data can be stored in computer memory: contiguous memory storage and scattered memory storage. The characteristics of the two complement each other.
  • Arrays support random access and use less memory; however, inserting and deleting elements is inefficient, and the length is immutable after initialization.
  • Linked lists achieve efficient insertion and deletion of nodes by modifying references (pointers), and can flexibly adjust length; however, node access is inefficient and memory consumption is higher. Common linked list types include singly linked lists, circular linked lists, and doubly linked lists.
  • A list is an ordered collection of elements that supports insertion, deletion, search, and modification, typically implemented based on dynamic arrays. It retains the advantages of arrays while allowing flexible adjustment of length.
  • The emergence of lists has greatly improved the practicality of arrays, but may result in some wasted memory space.
  • During program execution, data is primarily stored in memory. Arrays provide higher memory space efficiency, while linked lists offer greater flexibility in memory usage.
  • Caches provide fast data access to the CPU through mechanisms such as cache lines, prefetching, and spatial and temporal locality, significantly improving program execution efficiency.
  • Because arrays have higher cache hit rates, they are generally more efficient than linked lists. When choosing a data structure, appropriate selection should be made based on specific requirements and scenarios.
","path":["Chapter 4. Array and Linked List","4.5   Summary"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Does storing an array on the stack versus on the heap affect time efficiency and space efficiency?

Arrays stored on the stack and on the heap are both stored in contiguous memory space, so data operation efficiency is basically the same. However, the stack and heap have their own characteristics, leading to the following differences.

  1. Allocation and deallocation efficiency: The stack is a relatively small piece of memory, with allocation automatically handled by the compiler; the heap is relatively larger and can be dynamically allocated in code, more prone to fragmentation. Therefore, allocation and deallocation operations on the heap are usually slower than on the stack.
  2. Size limitations: Stack memory is relatively small, and the heap size is generally limited by available memory. Therefore, the heap is more suitable for storing large arrays.
  3. Flexibility: The size of an array on the stack must be determined at compile time, while the size of an array on the heap can be determined dynamically at runtime.

Q: Why do arrays require elements of the same type, while linked lists do not emphasize this requirement?

Linked lists are composed of nodes, with nodes connected through references (pointers), and each node can store different types of data, such as int, double, string, object, etc.

In contrast, array elements must be of the same type, so that the corresponding element position can be obtained by calculating the offset. For example, if an array contains both int and long types, with individual elements occupying 4 bytes and 8 bytes respectively, then the following formula cannot be used to calculate the offset, because the array contains two different \"element lengths\".

# Element Memory Address = Array Memory Address (first Element Memory address) + Element Length * Element Index\n

Q: After deleting node P, do we need to set P.next to None?

It is not necessary to modify P.next. From the perspective of the linked list, traversing from the head node to the tail node will no longer encounter P. This means that node P has been removed from the linked list, and it doesn't matter where node P points to at this time—it won't affect the linked list.

From a data structures and algorithms perspective (problem-solving), not disconnecting the pointer doesn't matter as long as the program logic is correct. From the perspective of standard libraries, disconnecting is safer and the logic is clearer. If not disconnected, assuming the deleted node is not properly reclaimed, it may affect the memory reclamation of its successor nodes.

Q: In a linked list, the time complexity of insertion and deletion operations is \\(O(1)\\). However, both insertion and deletion require \\(O(n)\\) time to find the element; why isn't the time complexity \\(O(n)\\)?

If the element is first found and then deleted, the time complexity is indeed \\(O(n)\\). However, the advantage of \\(O(1)\\) insertion and deletion in linked lists can be demonstrated in other applications. For example, a deque is well-suited for linked list implementation, where we maintain pointer variables always pointing to the head and tail nodes, with each insertion and deletion operation being \\(O(1)\\).

Q: In the diagram \"Linked List Definition and Storage Methods\", does the light blue pointer node occupy a single memory address, or does it share equally with the node value?

This diagram is a qualitative representation; a quantitative representation requires analysis based on the specific situation.

  • Different types of node values occupy different amounts of space, such as int, long, double, and instance objects, etc.
  • The amount of memory space occupied by pointer variables depends on the operating system and compilation environment used, usually 8 bytes or 4 bytes.

Q: Is appending an element at the end of a list always \\(O(1)\\)?

If appending an element exceeds the list length, the list must first be expanded before adding. The system allocates a new block of memory and moves all elements from the original list to it, in which case the time complexity becomes \\(O(n)\\).

Q: \"The emergence of lists has greatly improved the practicality of arrays, but may result in some wasted memory space\"—does this space waste refer to the memory occupied by additional variables such as capacity, length, and expansion factor?

This space waste mainly has two aspects: on one hand, lists typically set an initial length, which we may not need to fully utilize; on the other hand, to prevent frequent expansion, expansion generally multiplies by a coefficient, such as \\(\\times 1.5\\). As a result, there will be many empty positions that we typically cannot completely fill.

Q: In Python, after initializing n = [1, 2, 3], the addresses of these 3 elements are contiguous, but initializing m = [2, 1, 3] reveals that each element's id is not continuous; rather, they are the same as those in n. Since the addresses of these elements are not contiguous, is m still an array?

If we replace list elements with linked list nodes n = [n1, n2, n3, n4, n5], usually these 5 node objects are also scattered throughout memory. However, given a list index, we can still obtain the node memory address in \\(O(1)\\) time, thereby accessing the corresponding node. This is because the array stores references to nodes, not the nodes themselves.

Unlike many languages, numbers in Python are wrapped as objects, and lists store not the numbers themselves, but references to the numbers. Therefore, we find that the same numbers in two arrays have the same id, and the memory addresses of these numbers need not be contiguous.

Q: C++ STL has std::list which has already implemented a doubly linked list, but it seems that some algorithm books don't use it directly. Is there a limitation?

On one hand, we often prefer to use arrays for implementing algorithms and only use linked lists when necessary, mainly for two reasons.

  • Space overhead: Since each element requires two additional pointers (one for the previous element and one for the next element), std::list typically consumes more space than std::vector.
  • Cache unfriendliness: Since data is not stored contiguously, std::list has lower cache utilization. In general, std::vector has better performance.

On the other hand, cases where linked lists are necessary mainly involve binary trees and graphs. Stacks and queues usually use the stack and queue provided by the programming language, rather than linked lists.

Q: Does the operation res = [[0]] * n create a 2D list where each [0] is independent?

No, they are not independent. In this 2D list, all the [0] are actually references to the same object. If we modify one element, we will find that all corresponding elements change accordingly.

If we want each [0] in the 2D list to be independent, we can use res = [[0] for _ in range(n)] to achieve this. The principle of this approach is to initialize \\(n\\) independent [0] list objects.

Q: Does the operation res = [0] * n create a list where each integer 0 is independent?

In this list, all integer 0s are references to the same object. This is because Python uses a caching mechanism for small integers (typically -5 to 256) to maximize object reuse and improve performance.

Although they point to the same object, we can still independently modify each element in the list. This is because Python integers are \"immutable objects\". When we modify an element, we are actually switching to a reference of another object, rather than changing the original object itself.

However, when list elements are \"mutable objects\" (such as lists, dictionaries, or class instances), modifying an element directly changes the object itself, and all elements referencing that object will have the same change.

","path":["Chapter 4. Array and Linked List","4.5   Summary"],"tags":[]},{"location":"chapter_backtracking/","level":1,"title":"Chapter 13.   Backtracking","text":"

Abstract

We are like explorers in a maze, and may encounter difficulties on the path forward.

The power of backtracking allows us to start over, keep trying, and eventually find the exit leading to light.

","path":["Chapter 13. Backtracking","Chapter 13.   Backtracking"],"tags":[]},{"location":"chapter_backtracking/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 13.1   Backtracking Algorithm
  • 13.2   Permutations Problem
  • 13.3   Subset-Sum Problem
  • 13.4   N-Queens Problem
  • 13.5   Summary
","path":["Chapter 13. Backtracking","Chapter 13.   Backtracking"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/","level":1,"title":"13.1   Backtracking Algorithm","text":"

The backtracking algorithm is a method for solving problems through exhaustive search. Its core idea is to start from an initial state and exhaustively search all possible solutions. When a correct solution is found, it is recorded. This process continues until a solution is found or all possible choices have been tried without finding a solution.

The backtracking algorithm typically employs \"depth-first search\" to traverse the solution space. In the \"Binary Tree\" chapter, we mentioned that preorder, inorder, and postorder traversals all belong to depth-first search. Next, we will construct a backtracking problem using preorder traversal to progressively understand how the backtracking algorithm works.

Example 1

Given a binary tree, search and record all nodes with value \\(7\\), and return a list of these nodes.

For this problem, we perform a preorder traversal of the tree and check whether the current node's value is \\(7\\). If it is, we add the node to the result list res. The relevant implementation is shown in the following figure and code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_i_compact.py
def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: Example 1\"\"\"\n    if root is None:\n        return\n    if root.val == 7:\n        # Record solution\n        res.append(root)\n    pre_order(root.left)\n    pre_order(root.right)\n
preorder_traversal_i_compact.cpp
/* Preorder traversal: Example 1 */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr) {\n        return;\n    }\n    if (root->val == 7) {\n        // Record solution\n        res.push_back(root);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n}\n
preorder_traversal_i_compact.java
/* Preorder traversal: Example 1 */\nvoid preOrder(TreeNode root) {\n    if (root == null) {\n        return;\n    }\n    if (root.val == 7) {\n        // Record solution\n        res.add(root);\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n}\n
preorder_traversal_i_compact.cs
/* Preorder traversal: Example 1 */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) {\n        return;\n    }\n    if (root.val == 7) {\n        // Record solution\n        res.Add(root);\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n}\n
preorder_traversal_i_compact.go
/* Preorder traversal: Example 1 */\nfunc preOrderI(root *TreeNode, res *[]*TreeNode) {\n    if root == nil {\n        return\n    }\n    if (root.Val).(int) == 7 {\n        // Record solution\n        *res = append(*res, root)\n    }\n    preOrderI(root.Left, res)\n    preOrderI(root.Right, res)\n}\n
preorder_traversal_i_compact.swift
/* Preorder traversal: Example 1 */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    if root.val == 7 {\n        // Record solution\n        res.append(root)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n}\n
preorder_traversal_i_compact.js
/* Preorder traversal: Example 1 */\nfunction preOrder(root, res) {\n    if (root === null) {\n        return;\n    }\n    if (root.val === 7) {\n        // Record solution\n        res.push(root);\n    }\n    preOrder(root.left, res);\n    preOrder(root.right, res);\n}\n
preorder_traversal_i_compact.ts
/* Preorder traversal: Example 1 */\nfunction preOrder(root: TreeNode | null, res: TreeNode[]): void {\n    if (root === null) {\n        return;\n    }\n    if (root.val === 7) {\n        // Record solution\n        res.push(root);\n    }\n    preOrder(root.left, res);\n    preOrder(root.right, res);\n}\n
preorder_traversal_i_compact.dart
/* Preorder traversal: Example 1 */\nvoid preOrder(TreeNode? root, List<TreeNode> res) {\n  if (root == null) {\n    return;\n  }\n  if (root.val == 7) {\n    // Record solution\n    res.add(root);\n  }\n  preOrder(root.left, res);\n  preOrder(root.right, res);\n}\n
preorder_traversal_i_compact.rs
/* Preorder traversal: Example 1 */\nfn pre_order(res: &mut Vec<Rc<RefCell<TreeNode>>>, root: Option<&Rc<RefCell<TreeNode>>>) {\n    if root.is_none() {\n        return;\n    }\n    if let Some(node) = root {\n        if node.borrow().val == 7 {\n            // Record solution\n            res.push(node.clone());\n        }\n        pre_order(res, node.borrow().left.as_ref());\n        pre_order(res, node.borrow().right.as_ref());\n    }\n}\n
preorder_traversal_i_compact.c
/* Preorder traversal: Example 1 */\nvoid preOrder(TreeNode *root) {\n    if (root == NULL) {\n        return;\n    }\n    if (root->val == 7) {\n        // Record solution\n        res[resSize++] = root;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n}\n
preorder_traversal_i_compact.kt
/* Preorder traversal: Example 1 */\nfun preOrder(root: TreeNode?) {\n    if (root == null) {\n        return\n    }\n    if (root._val == 7) {\n        // Record solution\n        res!!.add(root)\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n}\n
preorder_traversal_i_compact.rb
### Pre-order traversal: example 1 ###\ndef pre_order(root)\n  return unless root\n\n  # Record solution\n  $res << root if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\nend\n

Figure 13-1   Search for nodes in preorder traversal

","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1311-attempt-and-backtrack","level":2,"title":"13.1.1   Attempt and Backtrack","text":"

The reason it is called a backtracking algorithm is that it employs \"attempt\" and \"backtrack\" strategies when searching the solution space. When the algorithm encounters a state where it cannot continue forward or cannot find a solution that satisfies the constraints, it will undo the previous choice, return to a previous state, and try other possible choices.

For Example 1, visiting each node represents an \"attempt\", while skipping over a leaf node or a function return from the parent node represents a \"backtrack\".

It is worth noting that backtracking is not limited to function returns alone. To illustrate this, let's extend Example 1 slightly.

Example 2

In a binary tree, search all nodes with value \\(7\\), and return the paths from the root node to these nodes.

Based on the code from Example 1, we need to use a list path to record the visited node path. When we reach a node with value \\(7\\), we copy path and add it to the result list res. After traversal is complete, res contains all the solutions. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_ii_compact.py
def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: Example 2\"\"\"\n    if root is None:\n        return\n    # Attempt\n    path.append(root)\n    if root.val == 7:\n        # Record solution\n        res.append(list(path))\n    pre_order(root.left)\n    pre_order(root.right)\n    # Backtrack\n    path.pop()\n
preorder_traversal_ii_compact.cpp
/* Preorder traversal: Example 2 */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr) {\n        return;\n    }\n    // Attempt\n    path.push_back(root);\n    if (root->val == 7) {\n        // Record solution\n        res.push_back(path);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    path.pop_back();\n}\n
preorder_traversal_ii_compact.java
/* Preorder traversal: Example 2 */\nvoid preOrder(TreeNode root) {\n    if (root == null) {\n        return;\n    }\n    // Attempt\n    path.add(root);\n    if (root.val == 7) {\n        // Record solution\n        res.add(new ArrayList<>(path));\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n    // Backtrack\n    path.remove(path.size() - 1);\n}\n
preorder_traversal_ii_compact.cs
/* Preorder traversal: Example 2 */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) {\n        return;\n    }\n    // Attempt\n    path.Add(root);\n    if (root.val == 7) {\n        // Record solution\n        res.Add(new List<TreeNode>(path));\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n    // Backtrack\n    path.RemoveAt(path.Count - 1);\n}\n
preorder_traversal_ii_compact.go
/* Preorder traversal: Example 2 */\nfunc preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {\n    if root == nil {\n        return\n    }\n    // Attempt\n    *path = append(*path, root)\n    if root.Val.(int) == 7 {\n        // Record solution\n        *res = append(*res, append([]*TreeNode{}, *path...))\n    }\n    preOrderII(root.Left, res, path)\n    preOrderII(root.Right, res, path)\n    // Backtrack\n    *path = (*path)[:len(*path)-1]\n}\n
preorder_traversal_ii_compact.swift
/* Preorder traversal: Example 2 */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Attempt\n    path.append(root)\n    if root.val == 7 {\n        // Record solution\n        res.append(path)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n    // Backtrack\n    path.removeLast()\n}\n
preorder_traversal_ii_compact.js
/* Preorder traversal: Example 2 */\nfunction preOrder(root, path, res) {\n    if (root === null) {\n        return;\n    }\n    // Attempt\n    path.push(root);\n    if (root.val === 7) {\n        // Record solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
preorder_traversal_ii_compact.ts
/* Preorder traversal: Example 2 */\nfunction preOrder(\n    root: TreeNode | null,\n    path: TreeNode[],\n    res: TreeNode[][]\n): void {\n    if (root === null) {\n        return;\n    }\n    // Attempt\n    path.push(root);\n    if (root.val === 7) {\n        // Record solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
preorder_traversal_ii_compact.dart
/* Preorder traversal: Example 2 */\nvoid preOrder(\n  TreeNode? root,\n  List<TreeNode> path,\n  List<List<TreeNode>> res,\n) {\n  if (root == null) {\n    return;\n  }\n\n  // Attempt\n  path.add(root);\n  if (root.val == 7) {\n    // Record solution\n    res.add(List.from(path));\n  }\n  preOrder(root.left, path, res);\n  preOrder(root.right, path, res);\n  // Backtrack\n  path.removeLast();\n}\n
preorder_traversal_ii_compact.rs
/* Preorder traversal: Example 2 */\nfn pre_order(\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n    path: &mut Vec<Rc<RefCell<TreeNode>>>,\n    root: Option<&Rc<RefCell<TreeNode>>>,\n) {\n    if root.is_none() {\n        return;\n    }\n    if let Some(node) = root {\n        // Attempt\n        path.push(node.clone());\n        if node.borrow().val == 7 {\n            // Record solution\n            res.push(path.clone());\n        }\n        pre_order(res, path, node.borrow().left.as_ref());\n        pre_order(res, path, node.borrow().right.as_ref());\n        // Backtrack\n        path.pop();\n    }\n}\n
preorder_traversal_ii_compact.c
/* Preorder traversal: Example 2 */\nvoid preOrder(TreeNode *root) {\n    if (root == NULL) {\n        return;\n    }\n    // Attempt\n    path[pathSize++] = root;\n    if (root->val == 7) {\n        // Record solution\n        for (int i = 0; i < pathSize; ++i) {\n            res[resSize][i] = path[i];\n        }\n        resSize++;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    pathSize--;\n}\n
preorder_traversal_ii_compact.kt
/* Preorder traversal: Example 2 */\nfun preOrder(root: TreeNode?) {\n    if (root == null) {\n        return\n    }\n    // Attempt\n    path!!.add(root)\n    if (root._val == 7) {\n        // Record solution\n        res!!.add(path!!.toMutableList())\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n    // Backtrack\n    path!!.removeAt(path!!.size - 1)\n}\n
preorder_traversal_ii_compact.rb
### Pre-order traversal: example 2 ###\ndef pre_order(root)\n  return unless root\n\n  # Attempt\n  $path << root\n\n  # Record solution\n  $res << $path.dup if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\n\n  # Backtrack\n  $path.pop\nend\n

In each \"attempt\", we record the path by adding the current node to path; before \"backtracking\", we need to remove the node from path, to restore the state before this attempt.

Observing the process shown in the following figure, we can understand attempt and backtrack as \"advance\" and \"undo\", two operations that are the reverse of each other.

<1><2><3><4><5><6><7><8><9><10><11>

Figure 13-2   Attempt and backtrack

","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1312-pruning","level":2,"title":"13.1.2   Pruning","text":"

Complex backtracking problems usually contain one or more constraints. Constraints can typically be used for \"pruning\".

Example 3

In a binary tree, search all nodes with value \\(7\\) and return the paths from the root node to these nodes, but require that the paths do not contain nodes with value \\(3\\).

To satisfy the above constraints, we need to add pruning operations: during the search process, if we encounter a node with value \\(3\\), we return early and do not continue searching. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_iii_compact.py
def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: Example 3\"\"\"\n    # Pruning\n    if root is None or root.val == 3:\n        return\n    # Attempt\n    path.append(root)\n    if root.val == 7:\n        # Record solution\n        res.append(list(path))\n    pre_order(root.left)\n    pre_order(root.right)\n    # Backtrack\n    path.pop()\n
preorder_traversal_iii_compact.cpp
/* Preorder traversal: Example 3 */\nvoid preOrder(TreeNode *root) {\n    // Pruning\n    if (root == nullptr || root->val == 3) {\n        return;\n    }\n    // Attempt\n    path.push_back(root);\n    if (root->val == 7) {\n        // Record solution\n        res.push_back(path);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    path.pop_back();\n}\n
preorder_traversal_iii_compact.java
/* Preorder traversal: Example 3 */\nvoid preOrder(TreeNode root) {\n    // Pruning\n    if (root == null || root.val == 3) {\n        return;\n    }\n    // Attempt\n    path.add(root);\n    if (root.val == 7) {\n        // Record solution\n        res.add(new ArrayList<>(path));\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n    // Backtrack\n    path.remove(path.size() - 1);\n}\n
preorder_traversal_iii_compact.cs
/* Preorder traversal: Example 3 */\nvoid PreOrder(TreeNode? root) {\n    // Pruning\n    if (root == null || root.val == 3) {\n        return;\n    }\n    // Attempt\n    path.Add(root);\n    if (root.val == 7) {\n        // Record solution\n        res.Add(new List<TreeNode>(path));\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n    // Backtrack\n    path.RemoveAt(path.Count - 1);\n}\n
preorder_traversal_iii_compact.go
/* Preorder traversal: Example 3 */\nfunc preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {\n    // Pruning\n    if root == nil || root.Val == 3 {\n        return\n    }\n    // Attempt\n    *path = append(*path, root)\n    if root.Val.(int) == 7 {\n        // Record solution\n        *res = append(*res, append([]*TreeNode{}, *path...))\n    }\n    preOrderIII(root.Left, res, path)\n    preOrderIII(root.Right, res, path)\n    // Backtrack\n    *path = (*path)[:len(*path)-1]\n}\n
preorder_traversal_iii_compact.swift
/* Preorder traversal: Example 3 */\nfunc preOrder(root: TreeNode?) {\n    // Pruning\n    guard let root = root, root.val != 3 else {\n        return\n    }\n    // Attempt\n    path.append(root)\n    if root.val == 7 {\n        // Record solution\n        res.append(path)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n    // Backtrack\n    path.removeLast()\n}\n
preorder_traversal_iii_compact.js
/* Preorder traversal: Example 3 */\nfunction preOrder(root, path, res) {\n    // Pruning\n    if (root === null || root.val === 3) {\n        return;\n    }\n    // Attempt\n    path.push(root);\n    if (root.val === 7) {\n        // Record solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
preorder_traversal_iii_compact.ts
/* Preorder traversal: Example 3 */\nfunction preOrder(\n    root: TreeNode | null,\n    path: TreeNode[],\n    res: TreeNode[][]\n): void {\n    // Pruning\n    if (root === null || root.val === 3) {\n        return;\n    }\n    // Attempt\n    path.push(root);\n    if (root.val === 7) {\n        // Record solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
preorder_traversal_iii_compact.dart
/* Preorder traversal: Example 3 */\nvoid preOrder(\n  TreeNode? root,\n  List<TreeNode> path,\n  List<List<TreeNode>> res,\n) {\n  if (root == null || root.val == 3) {\n    return;\n  }\n\n  // Attempt\n  path.add(root);\n  if (root.val == 7) {\n    // Record solution\n    res.add(List.from(path));\n  }\n  preOrder(root.left, path, res);\n  preOrder(root.right, path, res);\n  // Backtrack\n  path.removeLast();\n}\n
preorder_traversal_iii_compact.rs
/* Preorder traversal: Example 3 */\nfn pre_order(\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n    path: &mut Vec<Rc<RefCell<TreeNode>>>,\n    root: Option<&Rc<RefCell<TreeNode>>>,\n) {\n    // Pruning\n    if root.is_none() || root.as_ref().unwrap().borrow().val == 3 {\n        return;\n    }\n    if let Some(node) = root {\n        // Attempt\n        path.push(node.clone());\n        if node.borrow().val == 7 {\n            // Record solution\n            res.push(path.clone());\n        }\n        pre_order(res, path, node.borrow().left.as_ref());\n        pre_order(res, path, node.borrow().right.as_ref());\n        // Backtrack\n        path.pop();\n    }\n}\n
preorder_traversal_iii_compact.c
/* Preorder traversal: Example 3 */\nvoid preOrder(TreeNode *root) {\n    // Pruning\n    if (root == NULL || root->val == 3) {\n        return;\n    }\n    // Attempt\n    path[pathSize++] = root;\n    if (root->val == 7) {\n        // Record solution\n        for (int i = 0; i < pathSize; i++) {\n            res[resSize][i] = path[i];\n        }\n        resSize++;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    pathSize--;\n}\n
preorder_traversal_iii_compact.kt
/* Preorder traversal: Example 3 */\nfun preOrder(root: TreeNode?) {\n    // Pruning\n    if (root == null || root._val == 3) {\n        return\n    }\n    // Attempt\n    path!!.add(root)\n    if (root._val == 7) {\n        // Record solution\n        res!!.add(path!!.toMutableList())\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n    // Backtrack\n    path!!.removeAt(path!!.size - 1)\n}\n
preorder_traversal_iii_compact.rb
### Pre-order traversal: example 3 ###\ndef pre_order(root)\n  # Pruning\n  return if !root || root.val == 3\n\n  # Attempt\n  $path.append(root)\n\n  # Record solution\n  $res << $path.dup if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\n\n  # Backtrack\n  $path.pop\nend\n

\"Pruning\" is a vivid term. As shown in the following figure, during the search process, we \"prune\" search branches that do not satisfy the constraints, avoiding many meaningless attempts and thus improving search efficiency.

Figure 13-3   Pruning according to constraints

","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1313-framework-code","level":2,"title":"13.1.3   Framework Code","text":"

Next, we attempt to extract the main framework of backtracking's \"attempt, backtrack, and pruning\", to improve code generality.

In the following framework code, state represents the current state of the problem, and choices represents the choices available in the current state:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
def backtrack(state: State, choices: list[choice], res: list[state]):\n    \"\"\"Backtracking algorithm framework\"\"\"\n    # Check if it is a solution\n    if is_solution(state):\n        # Record the solution\n        record_solution(state, res)\n        # Stop searching\n        return\n    # Traverse all choices\n    for choice in choices:\n        # Pruning: check if the choice is valid\n        if is_valid(state, choice):\n            # Attempt: make a choice and update the state\n            make_choice(state, choice)\n            backtrack(state, choices, res)\n            # Backtrack: undo the choice and restore to the previous state\n            undo_choice(state, choice)\n
/* Backtracking algorithm framework */\nvoid backtrack(State *state, vector<Choice *> &choices, vector<State *> &res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for (Choice choice : choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nvoid backtrack(State state, List<Choice> choices, List<State> res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for (Choice choice : choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nvoid Backtrack(State state, List<Choice> choices, List<State> res) {\n    // Check if it is a solution\n    if (IsSolution(state)) {\n        // Record the solution\n        RecordSolution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    foreach (Choice choice in choices) {\n        // Pruning: check if the choice is valid\n        if (IsValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            MakeChoice(state, choice);\n            Backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            UndoChoice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nfunc backtrack(state *State, choices []Choice, res *[]State) {\n    // Check if it is a solution\n    if isSolution(state) {\n        // Record the solution\n        recordSolution(state, res)\n        // Stop searching\n        return\n    }\n    // Traverse all choices\n    for _, choice := range choices {\n        // Pruning: check if the choice is valid\n        if isValid(state, choice) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice)\n            backtrack(state, choices, res)\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nfunc backtrack(state: inout State, choices: [Choice], res: inout [State]) {\n    // Check if it is a solution\n    if isSolution(state: state) {\n        // Record the solution\n        recordSolution(state: state, res: &res)\n        // Stop searching\n        return\n    }\n    // Traverse all choices\n    for choice in choices {\n        // Pruning: check if the choice is valid\n        if isValid(state: state, choice: choice) {\n            // Attempt: make a choice and update the state\n            makeChoice(state: &state, choice: choice)\n            backtrack(state: &state, choices: choices, res: &res)\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state: &state, choice: choice)\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nfunction backtrack(state, choices, res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for (let choice of choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nfunction backtrack(state: State, choices: Choice[], res: State[]): void {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for (let choice of choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nvoid backtrack(State state, List<Choice>, List<State> res) {\n  // Check if it is a solution\n  if (isSolution(state)) {\n    // Record the solution\n    recordSolution(state, res);\n    // Stop searching\n    return;\n  }\n  // Traverse all choices\n  for (Choice choice in choices) {\n    // Pruning: check if the choice is valid\n    if (isValid(state, choice)) {\n      // Attempt: make a choice and update the state\n      makeChoice(state, choice);\n      backtrack(state, choices, res);\n      // Backtrack: undo the choice and restore to the previous state\n      undoChoice(state, choice);\n    }\n  }\n}\n
/* Backtracking algorithm framework */\nfn backtrack(state: &mut State, choices: &Vec<Choice>, res: &mut Vec<State>) {\n    // Check if it is a solution\n    if is_solution(state) {\n        // Record the solution\n        record_solution(state, res);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for choice in choices {\n        // Pruning: check if the choice is valid\n        if is_valid(state, choice) {\n            // Attempt: make a choice and update the state\n            make_choice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore to the previous state\n            undo_choice(state, choice);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nvoid backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res, numRes);\n        // Stop searching\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < numChoices; i++) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, &choices[i])) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, &choices[i]);\n            backtrack(state, choices, numChoices, res, numRes);\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, &choices[i]);\n        }\n    }\n}\n
/* Backtracking algorithm framework */\nfun backtrack(state: State?, choices: List<Choice?>, res: List<State?>?) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res)\n        // Stop searching\n        return\n    }\n    // Traverse all choices\n    for (choice in choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make a choice and update the state\n            makeChoice(state, choice)\n            backtrack(state, choices, res)\n            // Backtrack: undo the choice and restore to the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
### Backtracking algorithm framework ###\ndef backtrack(state, choices, res)\n    # Check if it is a solution\n    if is_solution?(state)\n        # Record the solution\n        record_solution(state, res)\n        return\n    end\n\n    # Traverse all choices\n    for choice in choices\n        # Pruning: check if the choice is valid\n        if is_valid?(state, choice)\n            # Attempt: make a choice and update the state\n            make_choice(state, choice)\n            backtrack(state, choices, res)\n            # Backtrack: undo the choice and restore to the previous state\n            undo_choice(state, choice)\n        end\n    end\nend\n

Next, we solve Example 3 based on the framework code. The state state is the node traversal path, the choices choices are the left and right child nodes of the current node, and the result res is a list of paths:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_iii_template.py
def is_solution(state: list[TreeNode]) -> bool:\n    \"\"\"Check if the current state is a solution\"\"\"\n    return state and state[-1].val == 7\n\ndef record_solution(state: list[TreeNode], res: list[list[TreeNode]]):\n    \"\"\"Record solution\"\"\"\n    res.append(list(state))\n\ndef is_valid(state: list[TreeNode], choice: TreeNode) -> bool:\n    \"\"\"Check if the choice is valid under the current state\"\"\"\n    return choice is not None and choice.val != 3\n\ndef make_choice(state: list[TreeNode], choice: TreeNode):\n    \"\"\"Update state\"\"\"\n    state.append(choice)\n\ndef undo_choice(state: list[TreeNode], choice: TreeNode):\n    \"\"\"Restore state\"\"\"\n    state.pop()\n\ndef backtrack(\n    state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]\n):\n    \"\"\"Backtracking algorithm: Example 3\"\"\"\n    # Check if it is a solution\n    if is_solution(state):\n        # Record solution\n        record_solution(state, res)\n    # Traverse all choices\n    for choice in choices:\n        # Pruning: check if the choice is valid\n        if is_valid(state, choice):\n            # Attempt: make choice, update state\n            make_choice(state, choice)\n            # Proceed to the next round of selection\n            backtrack(state, [choice.left, choice.right], res)\n            # Backtrack: undo choice, restore to previous state\n            undo_choice(state, choice)\n
preorder_traversal_iii_template.cpp
/* Check if the current state is a solution */\nbool isSolution(vector<TreeNode *> &state) {\n    return !state.empty() && state.back()->val == 7;\n}\n\n/* Record solution */\nvoid recordSolution(vector<TreeNode *> &state, vector<vector<TreeNode *>> &res) {\n    res.push_back(state);\n}\n\n/* Check if the choice is valid under the current state */\nbool isValid(vector<TreeNode *> &state, TreeNode *choice) {\n    return choice != nullptr && choice->val != 3;\n}\n\n/* Update state */\nvoid makeChoice(vector<TreeNode *> &state, TreeNode *choice) {\n    state.push_back(choice);\n}\n\n/* Restore state */\nvoid undoChoice(vector<TreeNode *> &state, TreeNode *choice) {\n    state.pop_back();\n}\n\n/* Backtracking algorithm: Example 3 */\nvoid backtrack(vector<TreeNode *> &state, vector<TreeNode *> &choices, vector<vector<TreeNode *>> &res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record solution\n        recordSolution(state, res);\n    }\n    // Traverse all choices\n    for (TreeNode *choice : choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice);\n            // Proceed to the next round of selection\n            vector<TreeNode *> nextChoices{choice->left, choice->right};\n            backtrack(state, nextChoices, res);\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
preorder_traversal_iii_template.java
/* Check if the current state is a solution */\nboolean isSolution(List<TreeNode> state) {\n    return !state.isEmpty() && state.get(state.size() - 1).val == 7;\n}\n\n/* Record solution */\nvoid recordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n    res.add(new ArrayList<>(state));\n}\n\n/* Check if the choice is valid under the current state */\nboolean isValid(List<TreeNode> state, TreeNode choice) {\n    return choice != null && choice.val != 3;\n}\n\n/* Update state */\nvoid makeChoice(List<TreeNode> state, TreeNode choice) {\n    state.add(choice);\n}\n\n/* Restore state */\nvoid undoChoice(List<TreeNode> state, TreeNode choice) {\n    state.remove(state.size() - 1);\n}\n\n/* Backtracking algorithm: Example 3 */\nvoid backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record solution\n        recordSolution(state, res);\n    }\n    // Traverse all choices\n    for (TreeNode choice : choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice);\n            // Proceed to the next round of selection\n            backtrack(state, Arrays.asList(choice.left, choice.right), res);\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
preorder_traversal_iii_template.cs
/* Check if the current state is a solution */\nbool IsSolution(List<TreeNode> state) {\n    return state.Count != 0 && state[^1].val == 7;\n}\n\n/* Record solution */\nvoid RecordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n    res.Add(new List<TreeNode>(state));\n}\n\n/* Check if the choice is valid under the current state */\nbool IsValid(List<TreeNode> state, TreeNode choice) {\n    return choice != null && choice.val != 3;\n}\n\n/* Update state */\nvoid MakeChoice(List<TreeNode> state, TreeNode choice) {\n    state.Add(choice);\n}\n\n/* Restore state */\nvoid UndoChoice(List<TreeNode> state, TreeNode choice) {\n    state.RemoveAt(state.Count - 1);\n}\n\n/* Backtracking algorithm: Example 3 */\nvoid Backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> res) {\n    // Check if it is a solution\n    if (IsSolution(state)) {\n        // Record solution\n        RecordSolution(state, res);\n    }\n    // Traverse all choices\n    foreach (TreeNode choice in choices) {\n        // Pruning: check if the choice is valid\n        if (IsValid(state, choice)) {\n            // Attempt: make choice, update state\n            MakeChoice(state, choice);\n            // Proceed to the next round of selection\n            Backtrack(state, [choice.left!, choice.right!], res);\n            // Backtrack: undo choice, restore to previous state\n            UndoChoice(state, choice);\n        }\n    }\n}\n
preorder_traversal_iii_template.go
/* Check if the current state is a solution */\nfunc isSolution(state *[]*TreeNode) bool {\n    return len(*state) != 0 && (*state)[len(*state)-1].Val == 7\n}\n\n/* Record solution */\nfunc recordSolution(state *[]*TreeNode, res *[][]*TreeNode) {\n    *res = append(*res, append([]*TreeNode{}, *state...))\n}\n\n/* Check if the choice is valid under the current state */\nfunc isValid(state *[]*TreeNode, choice *TreeNode) bool {\n    return choice != nil && choice.Val != 3\n}\n\n/* Update state */\nfunc makeChoice(state *[]*TreeNode, choice *TreeNode) {\n    *state = append(*state, choice)\n}\n\n/* Restore state */\nfunc undoChoice(state *[]*TreeNode, choice *TreeNode) {\n    *state = (*state)[:len(*state)-1]\n}\n\n/* Backtracking algorithm: Example 3 */\nfunc backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) {\n    // Check if it is a solution\n    if isSolution(state) {\n        // Record solution\n        recordSolution(state, res)\n    }\n    // Traverse all choices\n    for _, choice := range *choices {\n        // Pruning: check if the choice is valid\n        if isValid(state, choice) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice)\n            // Proceed to the next round of selection\n            temp := make([]*TreeNode, 0)\n            temp = append(temp, choice.Left, choice.Right)\n            backtrackIII(state, &temp, res)\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
preorder_traversal_iii_template.swift
/* Check if the current state is a solution */\nfunc isSolution(state: [TreeNode]) -> Bool {\n    !state.isEmpty && state.last!.val == 7\n}\n\n/* Record solution */\nfunc recordSolution(state: [TreeNode], res: inout [[TreeNode]]) {\n    res.append(state)\n}\n\n/* Check if the choice is valid under the current state */\nfunc isValid(state: [TreeNode], choice: TreeNode?) -> Bool {\n    choice != nil && choice!.val != 3\n}\n\n/* Update state */\nfunc makeChoice(state: inout [TreeNode], choice: TreeNode) {\n    state.append(choice)\n}\n\n/* Restore state */\nfunc undoChoice(state: inout [TreeNode], choice: TreeNode) {\n    state.removeLast()\n}\n\n/* Backtracking algorithm: Example 3 */\nfunc backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) {\n    // Check if it is a solution\n    if isSolution(state: state) {\n        recordSolution(state: state, res: &res)\n    }\n    // Traverse all choices\n    for choice in choices {\n        // Pruning: check if the choice is valid\n        if isValid(state: state, choice: choice) {\n            // Attempt: make choice, update state\n            makeChoice(state: &state, choice: choice)\n            // Proceed to the next round of selection\n            backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res)\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state: &state, choice: choice)\n        }\n    }\n}\n
preorder_traversal_iii_template.js
/* Check if the current state is a solution */\nfunction isSolution(state) {\n    return state && state[state.length - 1]?.val === 7;\n}\n\n/* Record solution */\nfunction recordSolution(state, res) {\n    res.push([...state]);\n}\n\n/* Check if the choice is valid under the current state */\nfunction isValid(state, choice) {\n    return choice !== null && choice.val !== 3;\n}\n\n/* Update state */\nfunction makeChoice(state, choice) {\n    state.push(choice);\n}\n\n/* Restore state */\nfunction undoChoice(state) {\n    state.pop();\n}\n\n/* Backtracking algorithm: Example 3 */\nfunction backtrack(state, choices, res) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record solution\n        recordSolution(state, res);\n    }\n    // Traverse all choices\n    for (const choice of choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice);\n            // Proceed to the next round of selection\n            backtrack(state, [choice.left, choice.right], res);\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state);\n        }\n    }\n}\n
preorder_traversal_iii_template.ts
/* Check if the current state is a solution */\nfunction isSolution(state: TreeNode[]): boolean {\n    return state && state[state.length - 1]?.val === 7;\n}\n\n/* Record solution */\nfunction recordSolution(state: TreeNode[], res: TreeNode[][]): void {\n    res.push([...state]);\n}\n\n/* Check if the choice is valid under the current state */\nfunction isValid(state: TreeNode[], choice: TreeNode): boolean {\n    return choice !== null && choice.val !== 3;\n}\n\n/* Update state */\nfunction makeChoice(state: TreeNode[], choice: TreeNode): void {\n    state.push(choice);\n}\n\n/* Restore state */\nfunction undoChoice(state: TreeNode[]): void {\n    state.pop();\n}\n\n/* Backtracking algorithm: Example 3 */\nfunction backtrack(\n    state: TreeNode[],\n    choices: TreeNode[],\n    res: TreeNode[][]\n): void {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record solution\n        recordSolution(state, res);\n    }\n    // Traverse all choices\n    for (const choice of choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice);\n            // Proceed to the next round of selection\n            backtrack(state, [choice.left, choice.right], res);\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state);\n        }\n    }\n}\n
preorder_traversal_iii_template.dart
/* Check if the current state is a solution */\nbool isSolution(List<TreeNode> state) {\n  return state.isNotEmpty && state.last.val == 7;\n}\n\n/* Record solution */\nvoid recordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n  res.add(List.from(state));\n}\n\n/* Check if the choice is valid under the current state */\nbool isValid(List<TreeNode> state, TreeNode? choice) {\n  return choice != null && choice.val != 3;\n}\n\n/* Update state */\nvoid makeChoice(List<TreeNode> state, TreeNode? choice) {\n  state.add(choice!);\n}\n\n/* Restore state */\nvoid undoChoice(List<TreeNode> state, TreeNode? choice) {\n  state.removeLast();\n}\n\n/* Backtracking algorithm: Example 3 */\nvoid backtrack(\n  List<TreeNode> state,\n  List<TreeNode?> choices,\n  List<List<TreeNode>> res,\n) {\n  // Check if it is a solution\n  if (isSolution(state)) {\n    // Record solution\n    recordSolution(state, res);\n  }\n  // Traverse all choices\n  for (TreeNode? choice in choices) {\n    // Pruning: check if the choice is valid\n    if (isValid(state, choice)) {\n      // Attempt: make choice, update state\n      makeChoice(state, choice);\n      // Proceed to the next round of selection\n      backtrack(state, [choice!.left, choice.right], res);\n      // Backtrack: undo choice, restore to previous state\n      undoChoice(state, choice);\n    }\n  }\n}\n
preorder_traversal_iii_template.rs
/* Check if the current state is a solution */\nfn is_solution(state: &mut Vec<Rc<RefCell<TreeNode>>>) -> bool {\n    return !state.is_empty() && state.last().unwrap().borrow().val == 7;\n}\n\n/* Record solution */\nfn record_solution(\n    state: &mut Vec<Rc<RefCell<TreeNode>>>,\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n) {\n    res.push(state.clone());\n}\n\n/* Check if the choice is valid under the current state */\nfn is_valid(_: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Option<&Rc<RefCell<TreeNode>>>) -> bool {\n    return choice.is_some() && choice.unwrap().borrow().val != 3;\n}\n\n/* Update state */\nfn make_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Rc<RefCell<TreeNode>>) {\n    state.push(choice);\n}\n\n/* Restore state */\nfn undo_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, _: Rc<RefCell<TreeNode>>) {\n    state.pop();\n}\n\n/* Backtracking algorithm: Example 3 */\nfn backtrack(\n    state: &mut Vec<Rc<RefCell<TreeNode>>>,\n    choices: &Vec<Option<&Rc<RefCell<TreeNode>>>>,\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n) {\n    // Check if it is a solution\n    if is_solution(state) {\n        // Record solution\n        record_solution(state, res);\n    }\n    // Traverse all choices\n    for &choice in choices.iter() {\n        // Pruning: check if the choice is valid\n        if is_valid(state, choice) {\n            // Attempt: make choice, update state\n            make_choice(state, choice.unwrap().clone());\n            // Proceed to the next round of selection\n            backtrack(\n                state,\n                &vec![\n                    choice.unwrap().borrow().left.as_ref(),\n                    choice.unwrap().borrow().right.as_ref(),\n                ],\n                res,\n            );\n            // Backtrack: undo choice, restore to previous state\n            undo_choice(state, choice.unwrap().clone());\n        }\n    }\n}\n
preorder_traversal_iii_template.c
/* Check if the current state is a solution */\nbool isSolution(void) {\n    return pathSize > 0 && path[pathSize - 1]->val == 7;\n}\n\n/* Record solution */\nvoid recordSolution(void) {\n    for (int i = 0; i < pathSize; i++) {\n        res[resSize][i] = path[i];\n    }\n    resSize++;\n}\n\n/* Check if the choice is valid under the current state */\nbool isValid(TreeNode *choice) {\n    return choice != NULL && choice->val != 3;\n}\n\n/* Update state */\nvoid makeChoice(TreeNode *choice) {\n    path[pathSize++] = choice;\n}\n\n/* Restore state */\nvoid undoChoice(void) {\n    pathSize--;\n}\n\n/* Backtracking algorithm: Example 3 */\nvoid backtrack(TreeNode *choices[2]) {\n    // Check if it is a solution\n    if (isSolution()) {\n        // Record solution\n        recordSolution();\n    }\n    // Traverse all choices\n    for (int i = 0; i < 2; i++) {\n        TreeNode *choice = choices[i];\n        // Pruning: check if the choice is valid\n        if (isValid(choice)) {\n            // Attempt: make choice, update state\n            makeChoice(choice);\n            // Proceed to the next round of selection\n            TreeNode *nextChoices[2] = {choice->left, choice->right};\n            backtrack(nextChoices);\n            // Backtrack: undo choice, restore to previous state\n            undoChoice();\n        }\n    }\n}\n
preorder_traversal_iii_template.kt
/* Check if the current state is a solution */\nfun isSolution(state: MutableList<TreeNode?>): Boolean {\n    return state.isNotEmpty() && state[state.size - 1]?._val == 7\n}\n\n/* Record solution */\nfun recordSolution(state: MutableList<TreeNode?>?, res: MutableList<MutableList<TreeNode?>?>) {\n    res.add(state!!.toMutableList())\n}\n\n/* Check if the choice is valid under the current state */\nfun isValid(state: MutableList<TreeNode?>?, choice: TreeNode?): Boolean {\n    return choice != null && choice._val != 3\n}\n\n/* Update state */\nfun makeChoice(state: MutableList<TreeNode?>, choice: TreeNode?) {\n    state.add(choice)\n}\n\n/* Restore state */\nfun undoChoice(state: MutableList<TreeNode?>, choice: TreeNode?) {\n    state.removeLast()\n}\n\n/* Backtracking algorithm: Example 3 */\nfun backtrack(\n    state: MutableList<TreeNode?>,\n    choices: MutableList<TreeNode?>,\n    res: MutableList<MutableList<TreeNode?>?>\n) {\n    // Check if it is a solution\n    if (isSolution(state)) {\n        // Record solution\n        recordSolution(state, res)\n    }\n    // Traverse all choices\n    for (choice in choices) {\n        // Pruning: check if the choice is valid\n        if (isValid(state, choice)) {\n            // Attempt: make choice, update state\n            makeChoice(state, choice)\n            // Proceed to the next round of selection\n            backtrack(state, mutableListOf(choice!!.left, choice.right), res)\n            // Backtrack: undo choice, restore to previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
preorder_traversal_iii_template.rb
### Check if current state is solution ###\ndef is_solution?(state)\n  !state.empty? && state.last.val == 7\nend\n\n### Record solution ###\ndef record_solution(state, res)\n  res << state.dup\nend\n\n### Check if choice is valid in current state ###\ndef is_valid?(state, choice)\n  choice && choice.val != 3\nend\n\n### Update state ###\ndef make_choice(state, choice)\n  state << choice\nend\n\n### Restore state ###\ndef undo_choice(state, choice)\n  state.pop\nend\n\n### Backtracking: example 3 ###\ndef backtrack(state, choices, res)\n  # Check if it is a solution\n  record_solution(state, res) if is_solution?(state)\n\n  # Traverse all choices\n  for choice in choices\n    # Pruning: check if the choice is valid\n    if is_valid?(state, choice)\n      # Attempt: make choice, update state\n      make_choice(state, choice)\n      # Proceed to the next round of selection\n      backtrack(state, [choice.left, choice.right], res)\n      # Backtrack: undo choice, restore to previous state\n      undo_choice(state, choice)\n    end\n  end\nend\n

As per the problem statement, we should continue searching after finding a node with value \\(7\\). Therefore, we need to remove the return statement after recording the solution. The following figure compares the search process with and without the return statement.

Figure 13-4   Comparison of search process with and without return statement

Compared to code based on preorder traversal, code based on the backtracking algorithm framework appears more verbose, but has better generality. In fact, many backtracking problems can be solved within this framework. We only need to define state and choices for the specific problem and implement each method in the framework.

","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1314-common-terminology","level":2,"title":"13.1.4   Common Terminology","text":"

To analyze algorithmic problems more clearly, we summarize the meanings of common terminology used in backtracking algorithms and provide corresponding examples from Example 3, as shown in the following table.

Table 13-1   Common Backtracking Algorithm Terminology

Term Definition Example 3 Solution (solution) A solution is an answer that satisfies the specific conditions of a problem; there may be one or more solutions All paths from root to nodes with value \\(7\\) that satisfy the constraint Constraint (constraint) A constraint is a condition in the problem that limits the feasibility of solutions, typically used for pruning Paths do not contain nodes with value \\(3\\) State (state) State represents the situation of a problem at a certain moment, including the choices already made The currently visited node path, i.e., the path list of nodes Attempt (attempt) An attempt is the process of exploring the solution space according to available choices, including making choices, updating state, and checking if it is a solution Recursively visit left (right) child nodes, add nodes to path, check if node value is \\(7\\) Backtrack (backtracking) Backtracking refers to undoing previous choices and returning to a previous state when encountering a state that does not satisfy constraints Stop searching when passing over leaf nodes, ending node visits, or encountering nodes with value \\(3\\); function returns Pruning (pruning) Pruning is a method of avoiding meaningless search paths according to problem characteristics and constraints, which can improve search efficiency When encountering a node with value \\(3\\), do not continue searching

Tip

The concepts of problem, solution, state, etc. are universal and are involved in divide-and-conquer, backtracking, dynamic programming, greedy and other algorithms.

","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1315-advantages-and-limitations","level":2,"title":"13.1.5   Advantages and Limitations","text":"

The backtracking algorithm is essentially a depth-first search algorithm that tries all possible solutions until it finds one that satisfies the conditions. The advantage of this approach is that it can find all possible solutions, and with reasonable pruning operations, it achieves high efficiency.

However, when dealing with large-scale or complex problems, the running efficiency of the backtracking algorithm may be unacceptable.

  • Time: The backtracking algorithm usually needs to traverse all possibilities in the solution space, and the time complexity can reach exponential or factorial order.
  • Space: During recursive calls, the current state needs to be saved (such as paths, auxiliary variables used for pruning, etc.), and when the depth is large, the space requirement can become very large.

Nevertheless, the backtracking algorithm is still the best solution for certain search problems and constraint satisfaction problems. For these problems, since we cannot predict which choices will generate valid solutions, we must traverse all possible choices. In this case, the key is how to optimize efficiency. There are two common efficiency optimization methods.

  • Pruning: Avoid searching paths that are guaranteed not to produce solutions, thereby saving time and space.
  • Heuristic search: Introduce certain strategies or estimation values during the search process to prioritize searching paths that are most likely to produce valid solutions.
","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1316-typical-backtracking-examples","level":2,"title":"13.1.6   Typical Backtracking Examples","text":"

The backtracking algorithm can be used to solve many search problems, constraint satisfaction problems, and combinatorial optimization problems.

Search problems: The goal of these problems is to find solutions that satisfy specific conditions.

  • Permutation problem: Given a set, find all possible permutations and combinations.
  • Subset sum problem: Given a set and a target sum, find all subsets in the set whose elements sum to the target.
  • Tower of Hanoi: Given three pegs and a series of disks of different sizes, move all disks from one peg to another, moving only one disk at a time, and never placing a larger disk on a smaller disk.

Constraint satisfaction problems: The goal of these problems is to find solutions that satisfy all constraints.

  • N-Queens: Place \\(n\\) queens on an \\(n \\times n\\) chessboard such that they do not attack each other.
  • Sudoku: Fill numbers \\(1\\) to \\(9\\) in a \\(9 \\times 9\\) grid such that each row, column, and \\(3 \\times 3\\) subgrid contains no repeated digits.
  • Graph coloring: Given an undirected graph, color each vertex with the minimum number of colors such that adjacent vertices have different colors.

Combinatorial optimization problems: The goal of these problems is to find an optimal solution that satisfies certain conditions in a combinatorial space.

  • 0-1 Knapsack: Given a set of items and a knapsack, each item has a value and weight. Under the knapsack capacity constraint, select items to maximize total value.
  • Traveling Salesman Problem: Starting from a point in a graph, visit all other points exactly once and return to the starting point, finding the shortest path.
  • Maximum Clique: Given an undirected graph, find the largest complete subgraph, i.e., a subgraph where any two vertices are connected by an edge.

Note that for many combinatorial optimization problems, backtracking is not the optimal solution.

  • The 0-1 Knapsack problem is usually solved using dynamic programming to achieve higher time efficiency.
  • The Traveling Salesman Problem is a famous NP-Hard problem; common solutions include genetic algorithms and ant colony algorithms.
  • The Maximum Clique problem is a classical problem in graph theory and can be solved using heuristic algorithms such as greedy algorithms.
","path":["Chapter 13. Backtracking","13.1   Backtracking Algorithm"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/","level":1,"title":"13.4   N-Queens Problem","text":"

Question

According to the rules of chess, a queen can attack pieces that share the same row, column, or diagonal line. Given \\(n\\) queens and an \\(n \\times n\\) chessboard, find a placement scheme such that no two queens can attack each other.

As shown in Figure 13-15, when \\(n = 4\\), there are two solutions that can be found. From the perspective of the backtracking algorithm, an \\(n \\times n\\) chessboard has \\(n^2\\) squares, which provide all the choices choices. During the process of placing queens one by one, the chessboard state changes continuously, and the chessboard at each moment represents the state state.

Figure 13-15   Solution to the 4-queens problem

Figure 13-16 illustrates the three constraints of this problem: multiple queens cannot be in the same row, the same column, or on the same diagonal. It is worth noting that diagonals are divided into two types: the main diagonal \\ and the anti-diagonal /.

Figure 13-16   Constraints of the n-queens problem

","path":["Chapter 13. Backtracking","13.4   N-Queens Problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#1-row-by-row-placement-strategy","level":3,"title":"1.   Row-By-Row Placement Strategy","text":"

Since both the number of queens and the number of rows on the chessboard are \\(n\\), we can easily derive a conclusion: each row of the chessboard allows and only allows exactly one queen to be placed.

This means we can adopt a row-by-row placement strategy: starting from the first row, place one queen in each row until the last row is completed.

Figure 13-17 shows the row-by-row placement process for the 4-queens problem. Due to space limitations, the figure only expands one search branch of the first row, and all schemes that do not satisfy the column constraint and diagonal constraints are pruned.

Figure 13-17   Row-by-row placement strategy

Essentially, the row-by-row placement strategy serves a pruning function, as it avoids all search branches where multiple queens appear in the same row.

","path":["Chapter 13. Backtracking","13.4   N-Queens Problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#2-column-and-diagonal-pruning","level":3,"title":"2.   Column and Diagonal Pruning","text":"

To satisfy the column constraint, we can use a boolean array cols of length \\(n\\) to record whether each column has a queen. Before each placement decision, we use cols to prune columns that already have queens, and dynamically update the state of cols during backtracking.

Tip

Please note that the origin of the matrix is located in the upper-left corner, where the row index increases from top to bottom, and the column index increases from left to right.

So how do we handle diagonal constraints? Consider a square on the chessboard with row and column indices \\((row, col)\\). If we select a specific main diagonal in the matrix, we find that all squares on that diagonal have the same difference between their row and column indices, meaning that \\(row - col\\) is a constant value for all squares on the main diagonal.

In other words, if two squares satisfy \\(row_1 - col_1 = row_2 - col_2\\), they must be on the same main diagonal. Using this pattern, we can use the array diags1 shown in Figure 13-18 to record whether there is a queen on each main diagonal.

Similarly, for all squares on an anti-diagonal, the sum \\(row + col\\) is a constant value. We can likewise use the array diags2 to handle anti-diagonal constraints.

Figure 13-18   Handling column and diagonal constraints

","path":["Chapter 13. Backtracking","13.4   N-Queens Problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#3-code-implementation","level":3,"title":"3.   Code Implementation","text":"

Please note that in an \\(n\\)-dimensional square matrix, the range of \\(row - col\\) is \\([-n + 1, n - 1]\\), and the range of \\(row + col\\) is \\([0, 2n - 2]\\). Therefore, the number of both main diagonals and anti-diagonals is \\(2n - 1\\), meaning the length of both arrays diags1 and diags2 is \\(2n - 1\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby n_queens.py
def backtrack(\n    row: int,\n    n: int,\n    state: list[list[str]],\n    res: list[list[list[str]]],\n    cols: list[bool],\n    diags1: list[bool],\n    diags2: list[bool],\n):\n    \"\"\"Backtracking algorithm: N queens\"\"\"\n    # When all rows are placed, record the solution\n    if row == n:\n        res.append([list(row) for row in state])\n        return\n    # Traverse all columns\n    for col in range(n):\n        # Calculate the main diagonal and anti-diagonal corresponding to this cell\n        diag1 = row - col + n - 1\n        diag2 = row + col\n        # Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if not cols[col] and not diags1[diag1] and not diags2[diag2]:\n            # Attempt: place the queen in this cell\n            state[row][col] = \"Q\"\n            cols[col] = diags1[diag1] = diags2[diag2] = True\n            # Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2)\n            # Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\"\n            cols[col] = diags1[diag1] = diags2[diag2] = False\n\ndef n_queens(n: int) -> list[list[list[str]]]:\n    \"\"\"Solve N queens\"\"\"\n    # Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    state = [[\"#\" for _ in range(n)] for _ in range(n)]\n    cols = [False] * n  # Record whether there is a queen in the column\n    diags1 = [False] * (2 * n - 1)  # Record whether there is a queen on the main diagonal\n    diags2 = [False] * (2 * n - 1)  # Record whether there is a queen on the anti-diagonal\n    res = []\n    backtrack(0, n, state, res, cols, diags1, diags2)\n\n    return res\n
n_queens.cpp
/* Backtracking algorithm: N queens */\nvoid backtrack(int row, int n, vector<vector<string>> &state, vector<vector<vector<string>>> &res, vector<bool> &cols,\n               vector<bool> &diags1, vector<bool> &diags2) {\n    // When all rows are placed, record the solution\n    if (row == n) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = \"Q\";\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\";\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nvector<vector<vector<string>>> nQueens(int n) {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    vector<vector<string>> state(n, vector<string>(n, \"#\"));\n    vector<bool> cols(n, false);           // Record whether there is a queen in the column\n    vector<bool> diags1(2 * n - 1, false); // Record whether there is a queen on the main diagonal\n    vector<bool> diags2(2 * n - 1, false); // Record whether there is a queen on the anti-diagonal\n    vector<vector<vector<string>>> res;\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
n_queens.java
/* Backtracking algorithm: N queens */\nvoid backtrack(int row, int n, List<List<String>> state, List<List<List<String>>> res,\n        boolean[] cols, boolean[] diags1, boolean[] diags2) {\n    // When all rows are placed, record the solution\n    if (row == n) {\n        List<List<String>> copyState = new ArrayList<>();\n        for (List<String> sRow : state) {\n            copyState.add(new ArrayList<>(sRow));\n        }\n        res.add(copyState);\n        return;\n    }\n    // Traverse all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state.get(row).set(col, \"Q\");\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state.get(row).set(col, \"#\");\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nList<List<List<String>>> nQueens(int n) {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    List<List<String>> state = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        List<String> row = new ArrayList<>();\n        for (int j = 0; j < n; j++) {\n            row.add(\"#\");\n        }\n        state.add(row);\n    }\n    boolean[] cols = new boolean[n]; // Record whether there is a queen in the column\n    boolean[] diags1 = new boolean[2 * n - 1]; // Record whether there is a queen on the main diagonal\n    boolean[] diags2 = new boolean[2 * n - 1]; // Record whether there is a queen on the anti-diagonal\n    List<List<List<String>>> res = new ArrayList<>();\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
n_queens.cs
/* Backtracking algorithm: N queens */\nvoid Backtrack(int row, int n, List<List<string>> state, List<List<List<string>>> res,\n        bool[] cols, bool[] diags1, bool[] diags2) {\n    // When all rows are placed, record the solution\n    if (row == n) {\n        List<List<string>> copyState = [];\n        foreach (List<string> sRow in state) {\n            copyState.Add(new List<string>(sRow));\n        }\n        res.Add(copyState);\n        return;\n    }\n    // Traverse all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = \"Q\";\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            Backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\";\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nList<List<List<string>>> NQueens(int n) {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    List<List<string>> state = [];\n    for (int i = 0; i < n; i++) {\n        List<string> row = [];\n        for (int j = 0; j < n; j++) {\n            row.Add(\"#\");\n        }\n        state.Add(row);\n    }\n    bool[] cols = new bool[n]; // Record whether there is a queen in the column\n    bool[] diags1 = new bool[2 * n - 1]; // Record whether there is a queen on the main diagonal\n    bool[] diags2 = new bool[2 * n - 1]; // Record whether there is a queen on the anti-diagonal\n    List<List<List<string>>> res = [];\n\n    Backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
n_queens.go
/* Backtracking algorithm: N queens */\nfunc backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) {\n    // When all rows are placed, record the solution\n    if row == n {\n        newState := make([][]string, len(*state))\n        for i, _ := range newState {\n            newState[i] = make([]string, len((*state)[0]))\n            copy(newState[i], (*state)[i])\n\n        }\n        *res = append(*res, newState)\n        return\n    }\n    // Traverse all columns\n    for col := 0; col < n; col++ {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        diag1 := row - col + n - 1\n        diag2 := row + col\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] {\n            // Attempt: place the queen in this cell\n            (*state)[row][col] = \"Q\"\n            (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true\n            // Place the next row\n            backtrack(row+1, n, state, res, cols, diags1, diags2)\n            // Backtrack: restore this cell to an empty cell\n            (*state)[row][col] = \"#\"\n            (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false\n        }\n    }\n}\n\n/* Solve N queens */\nfunc nQueens(n int) [][][]string {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    state := make([][]string, n)\n    for i := 0; i < n; i++ {\n        row := make([]string, n)\n        for i := 0; i < n; i++ {\n            row[i] = \"#\"\n        }\n        state[i] = row\n    }\n    // Record whether there is a queen in the column\n    cols := make([]bool, n)\n    diags1 := make([]bool, 2*n-1)\n    diags2 := make([]bool, 2*n-1)\n    res := make([][][]string, 0)\n    backtrack(0, n, &state, &res, &cols, &diags1, &diags2)\n    return res\n}\n
n_queens.swift
/* Backtracking algorithm: N queens */\nfunc backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) {\n    // When all rows are placed, record the solution\n    if row == n {\n        res.append(state)\n        return\n    }\n    // Traverse all columns\n    for col in 0 ..< n {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        let diag1 = row - col + n - 1\n        let diag2 = row + col\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if !cols[col] && !diags1[diag1] && !diags2[diag2] {\n            // Attempt: place the queen in this cell\n            state[row][col] = \"Q\"\n            cols[col] = true\n            diags1[diag1] = true\n            diags2[diag2] = true\n            // Place the next row\n            backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2)\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\"\n            cols[col] = false\n            diags1[diag1] = false\n            diags2[diag2] = false\n        }\n    }\n}\n\n/* Solve N queens */\nfunc nQueens(n: Int) -> [[[String]]] {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    var state = Array(repeating: Array(repeating: \"#\", count: n), count: n)\n    var cols = Array(repeating: false, count: n) // Record whether there is a queen in the column\n    var diags1 = Array(repeating: false, count: 2 * n - 1) // Record whether there is a queen on the main diagonal\n    var diags2 = Array(repeating: false, count: 2 * n - 1) // Record whether there is a queen on the anti-diagonal\n    var res: [[[String]]] = []\n\n    backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2)\n\n    return res\n}\n
n_queens.js
/* Backtracking algorithm: N queens */\nfunction backtrack(row, n, state, res, cols, diags1, diags2) {\n    // When all rows are placed, record the solution\n    if (row === n) {\n        res.push(state.map((row) => row.slice()));\n        return;\n    }\n    // Traverse all columns\n    for (let col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        const diag1 = row - col + n - 1;\n        const diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nfunction nQueens(n) {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    const state = Array.from({ length: n }, () => Array(n).fill('#'));\n    const cols = Array(n).fill(false); // Record whether there is a queen in the column\n    const diags1 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the main diagonal\n    const diags2 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the anti-diagonal\n    const res = [];\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n    return res;\n}\n
n_queens.ts
/* Backtracking algorithm: N queens */\nfunction backtrack(\n    row: number,\n    n: number,\n    state: string[][],\n    res: string[][][],\n    cols: boolean[],\n    diags1: boolean[],\n    diags2: boolean[]\n): void {\n    // When all rows are placed, record the solution\n    if (row === n) {\n        res.push(state.map((row) => row.slice()));\n        return;\n    }\n    // Traverse all columns\n    for (let col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        const diag1 = row - col + n - 1;\n        const diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nfunction nQueens(n: number): string[][][] {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    const state = Array.from({ length: n }, () => Array(n).fill('#'));\n    const cols = Array(n).fill(false); // Record whether there is a queen in the column\n    const diags1 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the main diagonal\n    const diags2 = Array(2 * n - 1).fill(false); // Record whether there is a queen on the anti-diagonal\n    const res: string[][][] = [];\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n    return res;\n}\n
n_queens.dart
/* Backtracking algorithm: N queens */\nvoid backtrack(\n  int row,\n  int n,\n  List<List<String>> state,\n  List<List<List<String>>> res,\n  List<bool> cols,\n  List<bool> diags1,\n  List<bool> diags2,\n) {\n  // When all rows are placed, record the solution\n  if (row == n) {\n    List<List<String>> copyState = [];\n    for (List<String> sRow in state) {\n      copyState.add(List.from(sRow));\n    }\n    res.add(copyState);\n    return;\n  }\n  // Traverse all columns\n  for (int col = 0; col < n; col++) {\n    // Calculate the main diagonal and anti-diagonal corresponding to this cell\n    int diag1 = row - col + n - 1;\n    int diag2 = row + col;\n    // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n    if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n      // Attempt: place the queen in this cell\n      state[row][col] = \"Q\";\n      cols[col] = true;\n      diags1[diag1] = true;\n      diags2[diag2] = true;\n      // Place the next row\n      backtrack(row + 1, n, state, res, cols, diags1, diags2);\n      // Backtrack: restore this cell to an empty cell\n      state[row][col] = \"#\";\n      cols[col] = false;\n      diags1[diag1] = false;\n      diags2[diag2] = false;\n    }\n  }\n}\n\n/* Solve N queens */\nList<List<List<String>>> nQueens(int n) {\n  // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n  List<List<String>> state = List.generate(n, (index) => List.filled(n, \"#\"));\n  List<bool> cols = List.filled(n, false); // Record whether there is a queen in the column\n  List<bool> diags1 = List.filled(2 * n - 1, false); // Record whether there is a queen on the main diagonal\n  List<bool> diags2 = List.filled(2 * n - 1, false); // Record whether there is a queen on the anti-diagonal\n  List<List<List<String>>> res = [];\n\n  backtrack(0, n, state, res, cols, diags1, diags2);\n\n  return res;\n}\n
n_queens.rs
/* Backtracking algorithm: N queens */\nfn backtrack(\n    row: usize,\n    n: usize,\n    state: &mut Vec<Vec<String>>,\n    res: &mut Vec<Vec<Vec<String>>>,\n    cols: &mut [bool],\n    diags1: &mut [bool],\n    diags2: &mut [bool],\n) {\n    // When all rows are placed, record the solution\n    if row == n {\n        res.push(state.clone());\n        return;\n    }\n    // Traverse all columns\n    for col in 0..n {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        let diag1 = row + n - 1 - col;\n        let diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if !cols[col] && !diags1[diag1] && !diags2[diag2] {\n            // Attempt: place the queen in this cell\n            state[row][col] = \"Q\".into();\n            (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true);\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\".into();\n            (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false);\n        }\n    }\n}\n\n/* Solve N queens */\nfn n_queens(n: usize) -> Vec<Vec<Vec<String>>> {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    let mut state: Vec<Vec<String>> = vec![vec![\"#\".to_string(); n]; n];\n    let mut cols = vec![false; n]; // Record whether there is a queen in the column\n    let mut diags1 = vec![false; 2 * n - 1]; // Record whether there is a queen on the main diagonal\n    let mut diags2 = vec![false; 2 * n - 1]; // Record whether there is a queen on the anti-diagonal\n    let mut res: Vec<Vec<Vec<String>>> = Vec::new();\n\n    backtrack(\n        0,\n        n,\n        &mut state,\n        &mut res,\n        &mut cols,\n        &mut diags1,\n        &mut diags2,\n    );\n\n    res\n}\n
n_queens.c
/* Backtracking algorithm: N queens */\nvoid backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE],\n               bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) {\n    // When all rows are placed, record the solution\n    if (row == n) {\n        res[*resSize] = (char **)malloc(sizeof(char *) * n);\n        for (int i = 0; i < n; ++i) {\n            res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1));\n            strcpy(res[*resSize][i], state[i]);\n        }\n        (*resSize)++;\n        return;\n    }\n    // Traverse all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place the next row\n            backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2);\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve N queens */\nchar ***nQueens(int n, int *returnSize) {\n    char state[MAX_SIZE][MAX_SIZE];\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    for (int i = 0; i < n; ++i) {\n        for (int j = 0; j < n; ++j) {\n            state[i][j] = '#';\n        }\n        state[i][n] = '\\0';\n    }\n    bool cols[MAX_SIZE] = {false};           // Record whether there is a queen in the column\n    bool diags1[2 * MAX_SIZE - 1] = {false}; // Record whether there is a queen on the main diagonal\n    bool diags2[2 * MAX_SIZE - 1] = {false}; // Record whether there is a queen on the anti-diagonal\n\n    char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE);\n    *returnSize = 0;\n    backtrack(0, n, state, res, returnSize, cols, diags1, diags2);\n    return res;\n}\n
n_queens.kt
/* Backtracking algorithm: N queens */\nfun backtrack(\n    row: Int,\n    n: Int,\n    state: MutableList<MutableList<String>>,\n    res: MutableList<MutableList<MutableList<String>>?>,\n    cols: BooleanArray,\n    diags1: BooleanArray,\n    diags2: BooleanArray\n) {\n    // When all rows are placed, record the solution\n    if (row == n) {\n        val copyState = mutableListOf<MutableList<String>>()\n        for (sRow in state) {\n            copyState.add(sRow.toMutableList())\n        }\n        res.add(copyState)\n        return\n    }\n    // Traverse all columns\n    for (col in 0..<n) {\n        // Calculate the main diagonal and anti-diagonal corresponding to this cell\n        val diag1 = row - col + n - 1\n        val diag2 = row + col\n        // Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Attempt: place the queen in this cell\n            state[row][col] = \"Q\"\n            diags2[diag2] = true\n            diags1[diag1] = diags2[diag2]\n            cols[col] = diags1[diag1]\n            // Place the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2)\n            // Backtrack: restore this cell to an empty cell\n            state[row][col] = \"#\"\n            diags2[diag2] = false\n            diags1[diag1] = diags2[diag2]\n            cols[col] = diags1[diag1]\n        }\n    }\n}\n\n/* Solve N queens */\nfun nQueens(n: Int): MutableList<MutableList<MutableList<String>>?> {\n    // Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n    val state = mutableListOf<MutableList<String>>()\n    for (i in 0..<n) {\n        val row = mutableListOf<String>()\n        for (j in 0..<n) {\n            row.add(\"#\")\n        }\n        state.add(row)\n    }\n    val cols = BooleanArray(n) // Record whether there is a queen in the column\n    val diags1 = BooleanArray(2 * n - 1) // Record whether there is a queen on the main diagonal\n    val diags2 = BooleanArray(2 * n - 1) // Record whether there is a queen on the anti-diagonal\n    val res = mutableListOf<MutableList<MutableList<String>>?>()\n\n    backtrack(0, n, state, res, cols, diags1, diags2)\n\n    return res\n}\n
n_queens.rb
### Backtracking: n queens ###\ndef backtrack(row, n, state, res, cols, diags1, diags2)\n  # When all rows are placed, record the solution\n  if row == n\n    res << state.map { |row| row.dup }\n    return\n  end\n\n  # Traverse all columns\n  for col in 0...n\n    # Calculate the main diagonal and anti-diagonal corresponding to this cell\n    diag1 = row - col + n - 1\n    diag2 = row + col\n    # Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell\n    if !cols[col] && !diags1[diag1] && !diags2[diag2]\n      # Attempt: place the queen in this cell\n      state[row][col] = \"Q\"\n      cols[col] = diags1[diag1] = diags2[diag2] = true\n      # Place the next row\n      backtrack(row + 1, n, state, res, cols, diags1, diags2)\n      # Backtrack: restore this cell to an empty cell\n      state[row][col] = \"#\"\n      cols[col] = diags1[diag1] = diags2[diag2] = false\n    end\n  end\nend\n\n### Solve n queens ###\ndef n_queens(n)\n  # Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell\n  state = Array.new(n) { Array.new(n, \"#\") }\n  cols = Array.new(n, false) # Record whether there is a queen in the column\n  diags1 = Array.new(2 * n - 1, false) # Record whether there is a queen on the main diagonal\n  diags2 = Array.new(2 * n - 1, false) # Record whether there is a queen on the anti-diagonal\n  res = []\n  backtrack(0, n, state, res, cols, diags1, diags2)\n\n  res\nend\n

Placing \\(n\\) queens row by row, considering the column constraint, from the first row to the last row there are \\(n\\), \\(n-1\\), \\(\\dots\\), \\(2\\), \\(1\\) choices, using \\(O(n!)\\) time. When recording a solution, it is necessary to copy the matrix state and add it to res, and the copy operation uses \\(O(n^2)\\) time. Therefore, the overall time complexity is \\(O(n! \\cdot n^2)\\). In practice, pruning based on diagonal constraints can also significantly reduce the search space, so the search efficiency is often better than the time complexity mentioned above.

The array state uses \\(O(n^2)\\) space, and the arrays cols, diags1, and diags2 each use \\(O(n)\\) space. The maximum recursion depth is \\(n\\), using \\(O(n)\\) stack frame space. Therefore, the space complexity is \\(O(n^2)\\).

","path":["Chapter 13. Backtracking","13.4   N-Queens Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/","level":1,"title":"13.2   Permutations Problem","text":"

The permutations problem is a classic application of backtracking algorithms. It is defined as finding all possible arrangements of elements in a given collection (such as an array or string).

Table 13-2 shows several example datasets, including input arrays and their corresponding permutations.

Table 13-2   Permutations Examples

Input Array All Permutations \\([1]\\) \\([1]\\) \\([1, 2]\\) \\([1, 2], [2, 1]\\) \\([1, 2, 3]\\) \\([1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]\\)","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1321-case-with-distinct-elements","level":2,"title":"13.2.1   Case with Distinct Elements","text":"

Question

Given an integer array with no duplicate elements, return all possible permutations.

From the perspective of backtracking algorithms, we can imagine the process of generating permutations as the result of a series of choices. Suppose the input array is \\([1, 2, 3]\\). If we first choose \\(1\\), then choose \\(3\\), and finally choose \\(2\\), we obtain the permutation \\([1, 3, 2]\\). Backtracking means undoing a choice and then trying other choices.

From the perspective of backtracking code, the candidate set choices consists of all elements in the input array, and the state state is the elements that have been chosen so far. Note that each element can only be chosen once, therefore all elements in state should be unique.

As shown in Figure 13-5, we can unfold the search process into a recursion tree, where each node in the tree represents the current state state. Starting from the root node, after three rounds of choices, we reach a leaf node, and each leaf node corresponds to a permutation.

Figure 13-5   Recursion tree of permutations

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1-pruning-duplicate-choices","level":3,"title":"1.   Pruning Duplicate Choices","text":"

To ensure that each element is chosen only once, we consider introducing a boolean array selected, where selected[i] indicates whether choices[i] has been chosen. We implement the following pruning operation based on it.

  • After making a choice choice[i], we set selected[i] to \\(\\text{True}\\), indicating that it has been chosen.
  • When traversing the candidate list choices, we skip all nodes that have been chosen, which is pruning.

As shown in Figure 13-6, suppose we choose \\(1\\) in the first round, \\(3\\) in the second round, and \\(2\\) in the third round. Then we need to prune the branch of element \\(1\\) in the second round and prune the branches of elements \\(1\\) and \\(3\\) in the third round.

Figure 13-6   Pruning example of permutations

Observing the above figure, we find that this pruning operation reduces the search space size from \\(O(n^n)\\) to \\(O(n!)\\).

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"

After understanding the above information, we can fill in the blanks in the template code. To shorten the overall code, we do not implement each function in the template separately, but instead unfold them in the backtrack() function:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby permutations_i.py
def backtrack(\n    state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]\n):\n    \"\"\"Backtracking algorithm: Permutations I\"\"\"\n    # When the state length equals the number of elements, record the solution\n    if len(state) == len(choices):\n        res.append(list(state))\n        return\n    # Traverse all choices\n    for i, choice in enumerate(choices):\n        # Pruning: do not allow repeated selection of elements\n        if not selected[i]:\n            # Attempt: make choice, update state\n            selected[i] = True\n            state.append(choice)\n            # Proceed to the next round of selection\n            backtrack(state, choices, selected, res)\n            # Backtrack: undo choice, restore to previous state\n            selected[i] = False\n            state.pop()\n\ndef permutations_i(nums: list[int]) -> list[list[int]]:\n    \"\"\"Permutations I\"\"\"\n    res = []\n    backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)\n    return res\n
permutations_i.cpp
/* Backtracking algorithm: Permutations I */\nvoid backtrack(vector<int> &state, const vector<int> &choices, vector<bool> &selected, vector<vector<int>> &res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size() == choices.size()) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choices.size(); i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.push_back(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop_back();\n        }\n    }\n}\n\n/* Permutations I */\nvector<vector<int>> permutationsI(vector<int> nums) {\n    vector<int> state;\n    vector<bool> selected(nums.size(), false);\n    vector<vector<int>> res;\n    backtrack(state, nums, selected, res);\n    return res;\n}\n
permutations_i.java
/* Backtracking algorithm: Permutations I */\nvoid backtrack(List<Integer> state, int[] choices, boolean[] selected, List<List<Integer>> res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size() == choices.length) {\n        res.add(new ArrayList<Integer>(state));\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choices.length; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.add(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.remove(state.size() - 1);\n        }\n    }\n}\n\n/* Permutations I */\nList<List<Integer>> permutationsI(int[] nums) {\n    List<List<Integer>> res = new ArrayList<List<Integer>>();\n    backtrack(new ArrayList<Integer>(), nums, new boolean[nums.length], res);\n    return res;\n}\n
permutations_i.cs
/* Backtracking algorithm: Permutations I */\nvoid Backtrack(List<int> state, int[] choices, bool[] selected, List<List<int>> res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.Count == choices.Length) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choices.Length; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.Add(choice);\n            // Proceed to the next round of selection\n            Backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.RemoveAt(state.Count - 1);\n        }\n    }\n}\n\n/* Permutations I */\nList<List<int>> PermutationsI(int[] nums) {\n    List<List<int>> res = [];\n    Backtrack([], nums, new bool[nums.Length], res);\n    return res;\n}\n
permutations_i.go
/* Backtracking algorithm: Permutations I */\nfunc backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {\n    // When the state length equals the number of elements, record the solution\n    if len(*state) == len(*choices) {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n    }\n    // Traverse all choices\n    for i := 0; i < len(*choices); i++ {\n        choice := (*choices)[i]\n        // Pruning: do not allow repeated selection of elements\n        if !(*selected)[i] {\n            // Attempt: make choice, update state\n            (*selected)[i] = true\n            *state = append(*state, choice)\n            // Proceed to the next round of selection\n            backtrackI(state, choices, selected, res)\n            // Backtrack: undo choice, restore to previous state\n            (*selected)[i] = false\n            *state = (*state)[:len(*state)-1]\n        }\n    }\n}\n\n/* Permutations I */\nfunc permutationsI(nums []int) [][]int {\n    res := make([][]int, 0)\n    state := make([]int, 0)\n    selected := make([]bool, len(nums))\n    backtrackI(&state, &nums, &selected, &res)\n    return res\n}\n
permutations_i.swift
/* Backtracking algorithm: Permutations I */\nfunc backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {\n    // When the state length equals the number of elements, record the solution\n    if state.count == choices.count {\n        res.append(state)\n        return\n    }\n    // Traverse all choices\n    for (i, choice) in choices.enumerated() {\n        // Pruning: do not allow repeated selection of elements\n        if !selected[i] {\n            // Attempt: make choice, update state\n            selected[i] = true\n            state.append(choice)\n            // Proceed to the next round of selection\n            backtrack(state: &state, choices: choices, selected: &selected, res: &res)\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false\n            state.removeLast()\n        }\n    }\n}\n\n/* Permutations I */\nfunc permutationsI(nums: [Int]) -> [[Int]] {\n    var state: [Int] = []\n    var selected = Array(repeating: false, count: nums.count)\n    var res: [[Int]] = []\n    backtrack(state: &state, choices: nums, selected: &selected, res: &res)\n    return res\n}\n
permutations_i.js
/* Backtracking algorithm: Permutations I */\nfunction backtrack(state, choices, selected, res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    choices.forEach((choice, i) => {\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations I */\nfunction permutationsI(nums) {\n    const res = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
permutations_i.ts
/* Backtracking algorithm: Permutations I */\nfunction backtrack(\n    state: number[],\n    choices: number[],\n    selected: boolean[],\n    res: number[][]\n): void {\n    // When the state length equals the number of elements, record the solution\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    choices.forEach((choice, i) => {\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations I */\nfunction permutationsI(nums: number[]): number[][] {\n    const res: number[][] = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
permutations_i.dart
/* Backtracking algorithm: Permutations I */\nvoid backtrack(\n  List<int> state,\n  List<int> choices,\n  List<bool> selected,\n  List<List<int>> res,\n) {\n  // When the state length equals the number of elements, record the solution\n  if (state.length == choices.length) {\n    res.add(List.from(state));\n    return;\n  }\n  // Traverse all choices\n  for (int i = 0; i < choices.length; i++) {\n    int choice = choices[i];\n    // Pruning: do not allow repeated selection of elements\n    if (!selected[i]) {\n      // Attempt: make choice, update state\n      selected[i] = true;\n      state.add(choice);\n      // Proceed to the next round of selection\n      backtrack(state, choices, selected, res);\n      // Backtrack: undo choice, restore to previous state\n      selected[i] = false;\n      state.removeLast();\n    }\n  }\n}\n\n/* Permutations I */\nList<List<int>> permutationsI(List<int> nums) {\n  List<List<int>> res = [];\n  backtrack([], nums, List.filled(nums.length, false), res);\n  return res;\n}\n
permutations_i.rs
/* Backtracking algorithm: Permutations I */\nfn backtrack(mut state: Vec<i32>, choices: &[i32], selected: &mut [bool], res: &mut Vec<Vec<i32>>) {\n    // When the state length equals the number of elements, record the solution\n    if state.len() == choices.len() {\n        res.push(state);\n        return;\n    }\n    // Traverse all choices\n    for i in 0..choices.len() {\n        let choice = choices[i];\n        // Pruning: do not allow repeated selection of elements\n        if !selected[i] {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state.clone(), choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    }\n}\n\n/* Permutations I */\nfn permutations_i(nums: &mut [i32]) -> Vec<Vec<i32>> {\n    let mut res = Vec::new(); // State (subset)\n    backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res);\n    res\n}\n
permutations_i.c
/* Backtracking algorithm: Permutations I */\nvoid backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {\n    // When the state length equals the number of elements, record the solution\n    if (stateSize == choicesSize) {\n        res[*resSize] = (int *)malloc(choicesSize * sizeof(int));\n        for (int i = 0; i < choicesSize; i++) {\n            res[*resSize][i] = state[i];\n        }\n        (*resSize)++;\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choicesSize; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true;\n            state[stateSize] = choice;\n            // Proceed to the next round of selection\n            backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n        }\n    }\n}\n\n/* Permutations I */\nint **permutationsI(int *nums, int numsSize, int *returnSize) {\n    int *state = (int *)malloc(numsSize * sizeof(int));\n    bool *selected = (bool *)malloc(numsSize * sizeof(bool));\n    for (int i = 0; i < numsSize; i++) {\n        selected[i] = false;\n    }\n    int **res = (int **)malloc(MAX_SIZE * sizeof(int *));\n    *returnSize = 0;\n\n    backtrack(state, 0, nums, numsSize, selected, res, returnSize);\n\n    free(state);\n    free(selected);\n\n    return res;\n}\n
permutations_i.kt
/* Backtracking algorithm: Permutations I */\nfun backtrack(\n    state: MutableList<Int>,\n    choices: IntArray,\n    selected: BooleanArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size == choices.size) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Traverse all choices\n    for (i in choices.indices) {\n        val choice = choices[i]\n        // Pruning: do not allow repeated selection of elements\n        if (!selected[i]) {\n            // Attempt: make choice, update state\n            selected[i] = true\n            state.add(choice)\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res)\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false\n            state.removeAt(state.size - 1)\n        }\n    }\n}\n\n/* Permutations I */\nfun permutationsI(nums: IntArray): MutableList<MutableList<Int>?> {\n    val res = mutableListOf<MutableList<Int>?>()\n    backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)\n    return res\n}\n
permutations_i.rb
### Backtracking: permutations I ###\ndef backtrack(state, choices, selected, res)\n  # When the state length equals the number of elements, record the solution\n  if state.length == choices.length\n    res << state.dup\n    return\n  end\n\n  # Traverse all choices\n  choices.each_with_index do |choice, i|\n    # Pruning: do not allow repeated selection of elements\n    unless selected[i]\n      # Attempt: make choice, update state\n      selected[i] = true\n      state << choice\n      # Proceed to the next round of selection\n      backtrack(state, choices, selected, res)\n      # Backtrack: undo choice, restore to previous state\n      selected[i] = false\n      state.pop\n    end\n  end\nend\n\n### Permutations I ###\ndef permutations_i(nums)\n  res = []\n  backtrack([], nums, Array.new(nums.length, false), res)\n  res\nend\n
","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1322-case-with-duplicate-elements","level":2,"title":"13.2.2   Case with Duplicate Elements","text":"

Question

Given an integer array that may contain duplicate elements, return all unique permutations.

Suppose the input array is \\([1, 1, 2]\\). To distinguish the two duplicate elements \\(1\\), we denote the second \\(1\\) as \\(\\hat{1}\\).

As shown in Figure 13-7, the method described above generates permutations where half are duplicates.

Figure 13-7   Duplicate permutations

So how do we remove duplicate permutations? The most direct approach is to use a hash set to directly deduplicate the permutation results. However, this is not elegant because the search branches that generate duplicate permutations are unnecessary and should be identified and pruned early, which can further improve algorithm efficiency.

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1-pruning-duplicate-elements","level":3,"title":"1.   Pruning Duplicate Elements","text":"

Observe Figure 13-8. In the first round, choosing \\(1\\) or choosing \\(\\hat{1}\\) is equivalent. All permutations generated under these two choices are duplicates. Therefore, we should prune \\(\\hat{1}\\).

Similarly, after choosing \\(2\\) in the first round, the \\(1\\) and \\(\\hat{1}\\) in the second round also produce duplicate branches, so the second round's \\(\\hat{1}\\) should also be pruned.

Essentially, our goal is to ensure that multiple equal elements are chosen only once in a certain round of choices.

Figure 13-8   Pruning duplicate permutations

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#2-code-implementation_1","level":3,"title":"2.   Code Implementation","text":"

Building on the code from the previous problem, we consider opening a hash set duplicated in each round of choices to record which elements have been tried in this round, and prune duplicate elements:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby permutations_ii.py
def backtrack(\n    state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]\n):\n    \"\"\"Backtracking algorithm: Permutations II\"\"\"\n    # When the state length equals the number of elements, record the solution\n    if len(state) == len(choices):\n        res.append(list(state))\n        return\n    # Traverse all choices\n    duplicated = set[int]()\n    for i, choice in enumerate(choices):\n        # Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if not selected[i] and choice not in duplicated:\n            # Attempt: make choice, update state\n            duplicated.add(choice)  # Record the selected element value\n            selected[i] = True\n            state.append(choice)\n            # Proceed to the next round of selection\n            backtrack(state, choices, selected, res)\n            # Backtrack: undo choice, restore to previous state\n            selected[i] = False\n            state.pop()\n\ndef permutations_ii(nums: list[int]) -> list[list[int]]:\n    \"\"\"Permutations II\"\"\"\n    res = []\n    backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)\n    return res\n
permutations_ii.cpp
/* Backtracking algorithm: Permutations II */\nvoid backtrack(vector<int> &state, const vector<int> &choices, vector<bool> &selected, vector<vector<int>> &res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size() == choices.size()) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all choices\n    unordered_set<int> duplicated;\n    for (int i = 0; i < choices.size(); i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && duplicated.find(choice) == duplicated.end()) {\n            // Attempt: make choice, update state\n            duplicated.emplace(choice); // Record the selected element value\n            selected[i] = true;\n            state.push_back(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop_back();\n        }\n    }\n}\n\n/* Permutations II */\nvector<vector<int>> permutationsII(vector<int> nums) {\n    vector<int> state;\n    vector<bool> selected(nums.size(), false);\n    vector<vector<int>> res;\n    backtrack(state, nums, selected, res);\n    return res;\n}\n
permutations_ii.java
/* Backtracking algorithm: Permutations II */\nvoid backtrack(List<Integer> state, int[] choices, boolean[] selected, List<List<Integer>> res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size() == choices.length) {\n        res.add(new ArrayList<Integer>(state));\n        return;\n    }\n    // Traverse all choices\n    Set<Integer> duplicated = new HashSet<Integer>();\n    for (int i = 0; i < choices.length; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated.contains(choice)) {\n            // Attempt: make choice, update state\n            duplicated.add(choice); // Record the selected element value\n            selected[i] = true;\n            state.add(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.remove(state.size() - 1);\n        }\n    }\n}\n\n/* Permutations II */\nList<List<Integer>> permutationsII(int[] nums) {\n    List<List<Integer>> res = new ArrayList<List<Integer>>();\n    backtrack(new ArrayList<Integer>(), nums, new boolean[nums.length], res);\n    return res;\n}\n
permutations_ii.cs
/* Backtracking algorithm: Permutations II */\nvoid Backtrack(List<int> state, int[] choices, bool[] selected, List<List<int>> res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.Count == choices.Length) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Traverse all choices\n    HashSet<int> duplicated = [];\n    for (int i = 0; i < choices.Length; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated.Contains(choice)) {\n            // Attempt: make choice, update state\n            duplicated.Add(choice); // Record the selected element value\n            selected[i] = true;\n            state.Add(choice);\n            // Proceed to the next round of selection\n            Backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.RemoveAt(state.Count - 1);\n        }\n    }\n}\n\n/* Permutations II */\nList<List<int>> PermutationsII(int[] nums) {\n    List<List<int>> res = [];\n    Backtrack([], nums, new bool[nums.Length], res);\n    return res;\n}\n
permutations_ii.go
/* Backtracking algorithm: Permutations II */\nfunc backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {\n    // When the state length equals the number of elements, record the solution\n    if len(*state) == len(*choices) {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n    }\n    // Traverse all choices\n    duplicated := make(map[int]struct{}, 0)\n    for i := 0; i < len(*choices); i++ {\n        choice := (*choices)[i]\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if _, ok := duplicated[choice]; !ok && !(*selected)[i] {\n            // Attempt: make choice, update state\n            // Record the selected element value\n            duplicated[choice] = struct{}{}\n            (*selected)[i] = true\n            *state = append(*state, choice)\n            // Proceed to the next round of selection\n            backtrackII(state, choices, selected, res)\n            // Backtrack: undo choice, restore to previous state\n            (*selected)[i] = false\n            *state = (*state)[:len(*state)-1]\n        }\n    }\n}\n\n/* Permutations II */\nfunc permutationsII(nums []int) [][]int {\n    res := make([][]int, 0)\n    state := make([]int, 0)\n    selected := make([]bool, len(nums))\n    backtrackII(&state, &nums, &selected, &res)\n    return res\n}\n
permutations_ii.swift
/* Backtracking algorithm: Permutations II */\nfunc backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {\n    // When the state length equals the number of elements, record the solution\n    if state.count == choices.count {\n        res.append(state)\n        return\n    }\n    // Traverse all choices\n    var duplicated: Set<Int> = []\n    for (i, choice) in choices.enumerated() {\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if !selected[i], !duplicated.contains(choice) {\n            // Attempt: make choice, update state\n            duplicated.insert(choice) // Record the selected element value\n            selected[i] = true\n            state.append(choice)\n            // Proceed to the next round of selection\n            backtrack(state: &state, choices: choices, selected: &selected, res: &res)\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false\n            state.removeLast()\n        }\n    }\n}\n\n/* Permutations II */\nfunc permutationsII(nums: [Int]) -> [[Int]] {\n    var state: [Int] = []\n    var selected = Array(repeating: false, count: nums.count)\n    var res: [[Int]] = []\n    backtrack(state: &state, choices: nums, selected: &selected, res: &res)\n    return res\n}\n
permutations_ii.js
/* Backtracking algorithm: Permutations II */\nfunction backtrack(state, choices, selected, res) {\n    // When the state length equals the number of elements, record the solution\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    const duplicated = new Set();\n    choices.forEach((choice, i) => {\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated.has(choice)) {\n            // Attempt: make choice, update state\n            duplicated.add(choice); // Record the selected element value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations II */\nfunction permutationsII(nums) {\n    const res = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
permutations_ii.ts
/* Backtracking algorithm: Permutations II */\nfunction backtrack(\n    state: number[],\n    choices: number[],\n    selected: boolean[],\n    res: number[][]\n): void {\n    // When the state length equals the number of elements, record the solution\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    const duplicated = new Set();\n    choices.forEach((choice, i) => {\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated.has(choice)) {\n            // Attempt: make choice, update state\n            duplicated.add(choice); // Record the selected element value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations II */\nfunction permutationsII(nums: number[]): number[][] {\n    const res: number[][] = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
permutations_ii.dart
/* Backtracking algorithm: Permutations II */\nvoid backtrack(\n  List<int> state,\n  List<int> choices,\n  List<bool> selected,\n  List<List<int>> res,\n) {\n  // When the state length equals the number of elements, record the solution\n  if (state.length == choices.length) {\n    res.add(List.from(state));\n    return;\n  }\n  // Traverse all choices\n  Set<int> duplicated = {};\n  for (int i = 0; i < choices.length; i++) {\n    int choice = choices[i];\n    // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n    if (!selected[i] && !duplicated.contains(choice)) {\n      // Attempt: make choice, update state\n      duplicated.add(choice); // Record the selected element value\n      selected[i] = true;\n      state.add(choice);\n      // Proceed to the next round of selection\n      backtrack(state, choices, selected, res);\n      // Backtrack: undo choice, restore to previous state\n      selected[i] = false;\n      state.removeLast();\n    }\n  }\n}\n\n/* Permutations II */\nList<List<int>> permutationsII(List<int> nums) {\n  List<List<int>> res = [];\n  backtrack([], nums, List.filled(nums.length, false), res);\n  return res;\n}\n
permutations_ii.rs
/* Backtracking algorithm: Permutations II */\nfn backtrack(mut state: Vec<i32>, choices: &[i32], selected: &mut [bool], res: &mut Vec<Vec<i32>>) {\n    // When the state length equals the number of elements, record the solution\n    if state.len() == choices.len() {\n        res.push(state);\n        return;\n    }\n    // Traverse all choices\n    let mut duplicated = HashSet::<i32>::new();\n    for i in 0..choices.len() {\n        let choice = choices[i];\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if !selected[i] && !duplicated.contains(&choice) {\n            // Attempt: make choice, update state\n            duplicated.insert(choice); // Record the selected element value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next round of selection\n            backtrack(state.clone(), choices, selected, res);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n            state.pop();\n        }\n    }\n}\n\n/* Permutations II */\nfn permutations_ii(nums: &mut [i32]) -> Vec<Vec<i32>> {\n    let mut res = Vec::new();\n    backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res);\n    res\n}\n
permutations_ii.c
/* Backtracking algorithm: Permutations II */\nvoid backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {\n    // When the state length equals the number of elements, record the solution\n    if (stateSize == choicesSize) {\n        res[*resSize] = (int *)malloc(choicesSize * sizeof(int));\n        for (int i = 0; i < choicesSize; i++) {\n            res[*resSize][i] = state[i];\n        }\n        (*resSize)++;\n        return;\n    }\n    // Traverse all choices\n    bool duplicated[MAX_SIZE] = {false};\n    for (int i = 0; i < choicesSize; i++) {\n        int choice = choices[i];\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated[choice]) {\n            // Attempt: make choice, update state\n            duplicated[choice] = true; // Record the selected element value\n            selected[i] = true;\n            state[stateSize] = choice;\n            // Proceed to the next round of selection\n            backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false;\n        }\n    }\n}\n\n/* Permutations II */\nint **permutationsII(int *nums, int numsSize, int *returnSize) {\n    int *state = (int *)malloc(numsSize * sizeof(int));\n    bool *selected = (bool *)malloc(numsSize * sizeof(bool));\n    for (int i = 0; i < numsSize; i++) {\n        selected[i] = false;\n    }\n    int **res = (int **)malloc(MAX_SIZE * sizeof(int *));\n    *returnSize = 0;\n\n    backtrack(state, 0, nums, numsSize, selected, res, returnSize);\n\n    free(state);\n    free(selected);\n\n    return res;\n}\n
permutations_ii.kt
/* Backtracking algorithm: Permutations II */\nfun backtrack(\n    state: MutableList<Int>,\n    choices: IntArray,\n    selected: BooleanArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // When the state length equals the number of elements, record the solution\n    if (state.size == choices.size) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Traverse all choices\n    val duplicated = HashSet<Int>()\n    for (i in choices.indices) {\n        val choice = choices[i]\n        // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n        if (!selected[i] && !duplicated.contains(choice)) {\n            // Attempt: make choice, update state\n            duplicated.add(choice) // Record the selected element value\n            selected[i] = true\n            state.add(choice)\n            // Proceed to the next round of selection\n            backtrack(state, choices, selected, res)\n            // Backtrack: undo choice, restore to previous state\n            selected[i] = false\n            state.removeAt(state.size - 1)\n        }\n    }\n}\n\n/* Permutations II */\nfun permutationsII(nums: IntArray): MutableList<MutableList<Int>?> {\n    val res = mutableListOf<MutableList<Int>?>()\n    backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)\n    return res\n}\n
permutations_ii.rb
### Backtracking: permutations II ###\ndef backtrack(state, choices, selected, res)\n  # When the state length equals the number of elements, record the solution\n  if state.length == choices.length\n    res << state.dup\n    return\n  end\n\n  # Traverse all choices\n  duplicated = Set.new\n  choices.each_with_index do |choice, i|\n    # Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements\n    if !selected[i] && !duplicated.include?(choice)\n      # Attempt: make choice, update state\n      duplicated.add(choice)\n      selected[i] = true\n      state << choice\n      # Proceed to the next round of selection\n      backtrack(state, choices, selected, res)\n      # Backtrack: undo choice, restore to previous state\n      selected[i] = false\n      state.pop\n    end\n  end\nend\n\n### Permutations II ###\ndef permutations_ii(nums)\n  res = []\n  backtrack([], nums, Array.new(nums.length, false), res)\n  res\nend\n

Assuming elements are pairwise distinct, there are \\(n!\\) (factorial) permutations of \\(n\\) elements. When recording results, we need to copy a list of length \\(n\\), using \\(O(n)\\) time. Therefore, the time complexity is \\(O(n! \\cdot n)\\).

The maximum recursion depth is \\(n\\), using \\(O(n)\\) stack frame space. selected uses \\(O(n)\\) space. At most \\(n\\) duplicated sets exist simultaneously, using \\(O(n^2)\\) space. Therefore, the space complexity is \\(O(n^2)\\).

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#3-comparison-of-two-pruning-methods","level":3,"title":"3.   Comparison of Two Pruning Methods","text":"

Note that although both selected and duplicated are used for pruning, they have different objectives.

  • Pruning duplicate choices: There is only one selected throughout the entire search process. It records which elements are included in the current state, and its purpose is to prevent an element from appearing repeatedly in state.
  • Pruning duplicate elements: Each round of choices (each backtrack function call) contains a duplicated set. It records which elements have been chosen in this round's iteration (the for loop), and its purpose is to ensure that equal elements are chosen only once.

Figure 13-9 shows the effective scope of the two pruning conditions. Note that each node in the tree represents a choice, and the nodes on the path from the root to a leaf node form a permutation.

Figure 13-9   Effective scope of two pruning conditions

","path":["Chapter 13. Backtracking","13.2   Permutations Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/","level":1,"title":"13.3   Subset-Sum Problem","text":"","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1331-without-duplicate-elements","level":2,"title":"13.3.1   Without Duplicate Elements","text":"

Question

Given a positive integer array nums and a target positive integer target, find all possible combinations where the sum of elements in the combination equals target. The given array has no duplicate elements, and each element can be selected multiple times. Return these combinations in list form, where the list should not contain duplicate combinations.

For example, given the set \\(\\{3, 4, 5\\}\\) and target integer \\(9\\), the solutions are \\(\\{3, 3, 3\\}, \\{4, 5\\}\\). Note the following two points:

  • Elements in the input set can be selected repeatedly without limit.
  • Subsets do not distinguish element order; for example, \\(\\{4, 5\\}\\) and \\(\\{5, 4\\}\\) are the same subset.
","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1-reference-to-full-permutation-solution","level":3,"title":"1.   Reference to Full Permutation Solution","text":"

Similar to the full permutation problem, we can imagine the process of generating subsets as a series of choices, and update the \"sum of elements\" in real-time during the selection process. When the sum equals target, we record the subset to the result list.

Unlike the full permutation problem, elements in this problem's set can be selected unlimited times, so we do not need to use a selected boolean list to track whether an element has been selected. We can make minor modifications to the full permutation code and initially obtain the solution:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_i_naive.py
def backtrack(\n    state: list[int],\n    target: int,\n    total: int,\n    choices: list[int],\n    res: list[list[int]],\n):\n    \"\"\"Backtracking algorithm: Subset sum I\"\"\"\n    # When the subset sum equals target, record the solution\n    if total == target:\n        res.append(list(state))\n        return\n    # Traverse all choices\n    for i in range(len(choices)):\n        # Pruning: if the subset sum exceeds target, skip this choice\n        if total + choices[i] > target:\n            continue\n        # Attempt: make choice, update element sum total\n        state.append(choices[i])\n        # Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res)\n        # Backtrack: undo choice, restore to previous state\n        state.pop()\n\ndef subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum I (including duplicate subsets)\"\"\"\n    state = []  # State (subset)\n    total = 0  # Subset sum\n    res = []  # Result list (subset list)\n    backtrack(state, target, total, nums, res)\n    return res\n
subset_sum_i_naive.cpp
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(vector<int> &state, int target, int total, vector<int> &choices, vector<vector<int>> &res) {\n    // When the subset sum equals target, record the solution\n    if (total == target) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all choices\n    for (size_t i = 0; i < choices.size(); i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.push_back(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nvector<vector<int>> subsetSumINaive(vector<int> &nums, int target) {\n    vector<int> state;       // State (subset)\n    int total = 0;           // Subset sum\n    vector<vector<int>> res; // Result list (subset list)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
subset_sum_i_naive.java
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(List<Integer> state, int target, int total, int[] choices, List<List<Integer>> res) {\n    // When the subset sum equals target, record the solution\n    if (total == target) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choices.length; i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.add(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nList<List<Integer>> subsetSumINaive(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    int total = 0; // Subset sum\n    List<List<Integer>> res = new ArrayList<>(); // Result list (subset list)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
subset_sum_i_naive.cs
/* Backtracking algorithm: Subset sum I */\nvoid Backtrack(List<int> state, int target, int total, int[] choices, List<List<int>> res) {\n    // When the subset sum equals target, record the solution\n    if (total == target) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choices.Length; i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.Add(choices[i]);\n        // Proceed to the next round of selection\n        Backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nList<List<int>> SubsetSumINaive(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    int total = 0; // Subset sum\n    List<List<int>> res = []; // Result list (subset list)\n    Backtrack(state, target, total, nums, res);\n    return res;\n}\n
subset_sum_i_naive.go
/* Backtracking algorithm: Subset sum I */\nfunc backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) {\n    // When the subset sum equals target, record the solution\n    if target == total {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Traverse all choices\n    for i := 0; i < len(*choices); i++ {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if total+(*choices)[i] > target {\n            continue\n        }\n        // Attempt: make choice, update element sum total\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next round of selection\n        backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res)\n        // Backtrack: undo choice, restore to previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfunc subsetSumINaive(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    total := 0              // Subset sum\n    res := make([][]int, 0) // Result list (subset list)\n    backtrackSubsetSumINaive(total, target, &state, &nums, &res)\n    return res\n}\n
subset_sum_i_naive.swift
/* Backtracking algorithm: Subset sum I */\nfunc backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) {\n    // When the subset sum equals target, record the solution\n    if total == target {\n        res.append(state)\n        return\n    }\n    // Traverse all choices\n    for i in choices.indices {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if total + choices[i] > target {\n            continue\n        }\n        // Attempt: make choice, update element sum total\n        state.append(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfunc subsetSumINaive(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let total = 0 // Subset sum\n    var res: [[Int]] = [] // Result list (subset list)\n    backtrack(state: &state, target: target, total: total, choices: nums, res: &res)\n    return res\n}\n
subset_sum_i_naive.js
/* Backtracking algorithm: Subset sum I */\nfunction backtrack(state, target, total, choices, res) {\n    // When the subset sum equals target, record the solution\n    if (total === target) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    for (let i = 0; i < choices.length; i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfunction subsetSumINaive(nums, target) {\n    const state = []; // State (subset)\n    const total = 0; // Subset sum\n    const res = []; // Result list (subset list)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
subset_sum_i_naive.ts
/* Backtracking algorithm: Subset sum I */\nfunction backtrack(\n    state: number[],\n    target: number,\n    total: number,\n    choices: number[],\n    res: number[][]\n): void {\n    // When the subset sum equals target, record the solution\n    if (total === target) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    for (let i = 0; i < choices.length; i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfunction subsetSumINaive(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    const total = 0; // Subset sum\n    const res = []; // Result list (subset list)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
subset_sum_i_naive.dart
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(\n  List<int> state,\n  int target,\n  int total,\n  List<int> choices,\n  List<List<int>> res,\n) {\n  // When the subset sum equals target, record the solution\n  if (total == target) {\n    res.add(List.from(state));\n    return;\n  }\n  // Traverse all choices\n  for (int i = 0; i < choices.length; i++) {\n    // Pruning: if the subset sum exceeds target, skip this choice\n    if (total + choices[i] > target) {\n      continue;\n    }\n    // Attempt: make choice, update element sum total\n    state.add(choices[i]);\n    // Proceed to the next round of selection\n    backtrack(state, target, total + choices[i], choices, res);\n    // Backtrack: undo choice, restore to previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nList<List<int>> subsetSumINaive(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  int total = 0; // Sum of elements\n  List<List<int>> res = []; // Result list (subset list)\n  backtrack(state, target, total, nums, res);\n  return res;\n}\n
subset_sum_i_naive.rs
/* Backtracking algorithm: Subset sum I */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    total: i32,\n    choices: &[i32],\n    res: &mut Vec<Vec<i32>>,\n) {\n    // When the subset sum equals target, record the solution\n    if total == target {\n        res.push(state.clone());\n        return;\n    }\n    // Traverse all choices\n    for i in 0..choices.len() {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if total + choices[i] > target {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    let total = 0; // Subset sum\n    let mut res = Vec::new(); // Result list (subset list)\n    backtrack(&mut state, target, total, nums, &mut res);\n    res\n}\n
subset_sum_i_naive.c
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(int target, int total, int *choices, int choicesSize) {\n    // When the subset sum equals target, record the solution\n    if (total == target) {\n        for (int i = 0; i < stateSize; i++) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Traverse all choices\n    for (int i = 0; i < choicesSize; i++) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Attempt: make choice, update element sum total\n        state[stateSize++] = choices[i];\n        // Proceed to the next round of selection\n        backtrack(target, total + choices[i], choices, choicesSize);\n        // Backtrack: undo choice, restore to previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nvoid subsetSumINaive(int *nums, int numsSize, int target) {\n    resSize = 0; // Initialize solution count to 0\n    backtrack(target, 0, nums, numsSize);\n}\n
subset_sum_i_naive.kt
/* Backtracking algorithm: Subset sum I */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    total: Int,\n    choices: IntArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // When the subset sum equals target, record the solution\n    if (total == target) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Traverse all choices\n    for (i in choices.indices) {\n        // Pruning: if the subset sum exceeds target, skip this choice\n        if (total + choices[i] > target) {\n            continue\n        }\n        // Attempt: make choice, update element sum total\n        state.add(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state, target, total + choices[i], choices, res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum I (including duplicate subsets) */\nfun subsetSumINaive(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    val total = 0 // Subset sum\n    val res = mutableListOf<MutableList<Int>?>() // Result list (subset list)\n    backtrack(state, target, total, nums, res)\n    return res\n}\n
subset_sum_i_naive.rb
### Backtracking: subset sum I ###\ndef backtrack(state, target, total, choices, res)\n  # When the subset sum equals target, record the solution\n  if total == target\n    res << state.dup\n    return\n  end\n\n  # Traverse all choices\n  for i in 0...choices.length\n    # Pruning: if the subset sum exceeds target, skip this choice\n    next if total + choices[i] > target\n    # Attempt: make choice, update element sum total\n    state << choices[i]\n    # Proceed to the next round of selection\n    backtrack(state, target, total + choices[i], choices, res)\n    # Backtrack: undo choice, restore to previous state\n    state.pop\n  end\nend\n\n### Solve subset sum I (with duplicate subsets) ###\ndef subset_sum_i_naive(nums, target)\n  state = [] # State (subset)\n  total = 0 # Subset sum\n  res = [] # Result list (subset list)\n  backtrack(state, target, total, nums, res)\n  res\nend\n

When we input array \\([3, 4, 5]\\) and target element \\(9\\) to the above code, the output is \\([3, 3, 3], [4, 5], [5, 4]\\). Although we successfully find all subsets that sum to \\(9\\), there are duplicate subsets \\([4, 5]\\) and \\([5, 4]\\).

This is because the search process distinguishes the order of selections, but subsets do not distinguish selection order. As shown in Figure 13-10, selecting 4 first and then 5 versus selecting 5 first and then 4 are different branches, but they correspond to the same subset.

Figure 13-10   Subset search and boundary pruning

To eliminate duplicate subsets, one straightforward idea is to deduplicate the result list. However, this approach is very inefficient for two reasons:

  • When there are many array elements, especially when target is large, the search process generates many duplicate subsets.
  • Comparing subsets (arrays) is very time-consuming, requiring sorting the arrays first, then comparing each element in them.
","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#2-pruning-duplicate-subsets","level":3,"title":"2.   Pruning Duplicate Subsets","text":"

We consider deduplication through pruning during the search process. Observing Figure 13-11, duplicate subsets occur when array elements are selected in different orders, as in the following cases:

  1. When the first and second rounds select \\(3\\) and \\(4\\) respectively, all subsets containing these two elements are generated, denoted as \\([3, 4, \\dots]\\).
  2. Afterward, when the first round selects \\(4\\), the second round should skip \\(3\\), because the subset \\([4, 3, \\dots]\\) generated by this choice is completely duplicate with the subset generated in step 1.

In the search process, each level's choices are tried from left to right, so the rightmost branches are pruned more.

  1. The first two rounds select \\(3\\) and \\(5\\), generating subset \\([3, 5, \\dots]\\).
  2. The first two rounds select \\(4\\) and \\(5\\), generating subset \\([4, 5, \\dots]\\).
  3. If the first round selects \\(5\\), the second round should skip \\(3\\) and \\(4\\), because subsets \\([5, 3, \\dots]\\) and \\([5, 4, \\dots]\\) are completely duplicate with the subsets described in steps 1. and 2.

Figure 13-11   Different selection orders leading to duplicate subsets

In summary, given an input array \\([x_1, x_2, \\dots, x_n]\\), let the selection sequence in the search process be \\([x_{i_1}, x_{i_2}, \\dots, x_{i_m}]\\). This selection sequence must satisfy \\(i_1 \\leq i_2 \\leq \\dots \\leq i_m\\); any selection sequence that does not satisfy this condition will cause duplicates and should be pruned.

","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#3-code-implementation","level":3,"title":"3.   Code Implementation","text":"

To implement this pruning, we initialize a variable start to indicate the starting point of traversal. After making choice \\(x_{i}\\), set the next round to start traversal from index \\(i\\). This ensures that the selection sequence satisfies \\(i_1 \\leq i_2 \\leq \\dots \\leq i_m\\), guaranteeing subset uniqueness.

In addition, we have made the following two optimizations to the code:

  • Before starting the search, first sort the array nums. When traversing all choices, end the loop immediately when the subset sum exceeds target, because subsequent elements are larger, and their subset sums must exceed target.
  • Omit the element sum variable total and use subtraction on target to track the sum of elements. Record the solution when target equals \\(0\\).
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_i.py
def backtrack(\n    state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]\n):\n    \"\"\"Backtracking algorithm: Subset sum I\"\"\"\n    # When the subset sum equals target, record the solution\n    if target == 0:\n        res.append(list(state))\n        return\n    # Traverse all choices\n    # Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for i in range(start, len(choices)):\n        # Pruning 1: if the subset sum exceeds target, end the loop directly\n        # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0:\n            break\n        # Attempt: make choice, update target, start\n        state.append(choices[i])\n        # Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res)\n        # Backtrack: undo choice, restore to previous state\n        state.pop()\n\ndef subset_sum_i(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum I\"\"\"\n    state = []  # State (subset)\n    nums.sort()  # Sort nums\n    start = 0  # Start point for traversal\n    res = []  # Result list (subset list)\n    backtrack(state, target, nums, start, res)\n    return res\n
subset_sum_i.cpp
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.size(); i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.push_back(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum I */\nvector<vector<int>> subsetSumI(vector<int> &nums, int target) {\n    vector<int> state;              // State (subset)\n    sort(nums.begin(), nums.end()); // Sort nums\n    int start = 0;                  // Start point for traversal\n    vector<vector<int>> res;        // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_i.java
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(List<Integer> state, int target, int[] choices, int start, List<List<Integer>> res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.add(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum I */\nList<List<Integer>> subsetSumI(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    Arrays.sort(nums); // Sort nums\n    int start = 0; // Start point for traversal\n    List<List<Integer>> res = new ArrayList<>(); // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_i.cs
/* Backtracking algorithm: Subset sum I */\nvoid Backtrack(List<int> state, int target, int[] choices, int start, List<List<int>> res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.Length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.Add(choices[i]);\n        // Proceed to the next round of selection\n        Backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum I */\nList<List<int>> SubsetSumI(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    Array.Sort(nums); // Sort nums\n    int start = 0; // Start point for traversal\n    List<List<int>> res = []; // Result list (subset list)\n    Backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_i.go
/* Backtracking algorithm: Subset sum I */\nfunc backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for i := start; i < len(*choices); i++ {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target-(*choices)[i] < 0 {\n            break\n        }\n        // Attempt: make choice, update target, start\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next round of selection\n        backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res)\n        // Backtrack: undo choice, restore to previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum I */\nfunc subsetSumI(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    sort.Ints(nums)         // Sort nums\n    start := 0              // Start point for traversal\n    res := make([][]int, 0) // Result list (subset list)\n    backtrackSubsetSumI(start, target, &state, &nums, &res)\n    return res\n}\n
subset_sum_i.swift
/* Backtracking algorithm: Subset sum I */\nfunc backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        res.append(state)\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for i in choices.indices.dropFirst(start) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0 {\n            break\n        }\n        // Attempt: make choice, update target, start\n        state.append(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum I */\nfunc subsetSumI(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let nums = nums.sorted() // Sort nums\n    let start = 0 // Start point for traversal\n    var res: [[Int]] = [] // Result list (subset list)\n    backtrack(state: &state, target: target, choices: nums, start: start, res: &res)\n    return res\n}\n
subset_sum_i.js
/* Backtracking algorithm: Subset sum I */\nfunction backtrack(state, target, choices, start, res) {\n    // When the subset sum equals target, record the solution\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfunction subsetSumI(nums, target) {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Start point for traversal\n    const res = []; // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_i.ts
/* Backtracking algorithm: Subset sum I */\nfunction backtrack(\n    state: number[],\n    target: number,\n    choices: number[],\n    start: number,\n    res: number[][]\n): void {\n    // When the subset sum equals target, record the solution\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfunction subsetSumI(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Start point for traversal\n    const res = []; // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_i.dart
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(\n  List<int> state,\n  int target,\n  List<int> choices,\n  int start,\n  List<List<int>> res,\n) {\n  // When the subset sum equals target, record the solution\n  if (target == 0) {\n    res.add(List.from(state));\n    return;\n  }\n  // Traverse all choices\n  // Pruning 2: start traversing from start to avoid generating duplicate subsets\n  for (int i = start; i < choices.length; i++) {\n    // Pruning 1: if the subset sum exceeds target, end the loop directly\n    // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n    if (target - choices[i] < 0) {\n      break;\n    }\n    // Attempt: make choice, update target, start\n    state.add(choices[i]);\n    // Proceed to the next round of selection\n    backtrack(state, target - choices[i], choices, i, res);\n    // Backtrack: undo choice, restore to previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum I */\nList<List<int>> subsetSumI(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  nums.sort(); // Sort nums\n  int start = 0; // Start point for traversal\n  List<List<int>> res = []; // Result list (subset list)\n  backtrack(state, target, nums, start, res);\n  return res;\n}\n
subset_sum_i.rs
/* Backtracking algorithm: Subset sum I */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    choices: &[i32],\n    start: usize,\n    res: &mut Vec<Vec<i32>>,\n) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        res.push(state.clone());\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for i in start..choices.len() {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0 {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfn subset_sum_i(nums: &mut [i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    nums.sort(); // Sort nums\n    let start = 0; // Start point for traversal\n    let mut res = Vec::new(); // Result list (subset list)\n    backtrack(&mut state, target, nums, start, &mut res);\n    res\n}\n
subset_sum_i.c
/* Backtracking algorithm: Subset sum I */\nvoid backtrack(int target, int *choices, int choicesSize, int start) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        for (int i = 0; i < stateSize; ++i) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (int i = start; i < choicesSize; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Attempt: make choice, update target, start\n        state[stateSize] = choices[i];\n        stateSize++;\n        // Proceed to the next round of selection\n        backtrack(target - choices[i], choices, choicesSize, i);\n        // Backtrack: undo choice, restore to previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum I */\nvoid subsetSumI(int *nums, int numsSize, int target) {\n    qsort(nums, numsSize, sizeof(int), cmp); // Sort nums\n    int start = 0;                           // Start point for traversal\n    backtrack(target, nums, numsSize, start);\n}\n
subset_sum_i.kt
/* Backtracking algorithm: Subset sum I */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    choices: IntArray,\n    start: Int,\n    res: MutableList<MutableList<Int>?>\n) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    for (i in start..<choices.size) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break\n        }\n        // Attempt: make choice, update target, start\n        state.add(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i, res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum I */\nfun subsetSumI(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    nums.sort() // Sort nums\n    val start = 0 // Start point for traversal\n    val res = mutableListOf<MutableList<Int>?>() // Result list (subset list)\n    backtrack(state, target, nums, start, res)\n    return res\n}\n
subset_sum_i.rb
### Backtracking: subset sum I ###\ndef backtrack(state, target, choices, start, res)\n  # When the subset sum equals target, record the solution\n  if target.zero?\n    res << state.dup\n    return\n  end\n  # Traverse all choices\n  # Pruning 2: start traversing from start to avoid generating duplicate subsets\n  for i in start...choices.length\n    # Pruning 1: if the subset sum exceeds target, end the loop directly\n    # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n    break if target - choices[i] < 0\n    # Attempt: make choice, update target, start\n    state << choices[i]\n    # Proceed to the next round of selection\n    backtrack(state, target - choices[i], choices, i, res)\n    # Backtrack: undo choice, restore to previous state\n    state.pop\n  end\nend\n\n### Solve subset sum I ###\ndef subset_sum_i(nums, target)\n  state = [] # State (subset)\n  nums.sort! # Sort nums\n  start = 0 # Start point for traversal\n  res = [] # Result list (subset list)\n  backtrack(state, target, nums, start, res)\n  res\nend\n

Figure 13-12 shows the complete backtracking process when array \\([3, 4, 5]\\) and target element \\(9\\) are input to the above code.

Figure 13-12   Subset-sum I backtracking process

","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1332-with-duplicate-elements-in-array","level":2,"title":"13.3.2   With Duplicate Elements in Array","text":"

Question

Given a positive integer array nums and a target positive integer target, find all possible combinations where the sum of elements in the combination equals target. The given array may contain duplicate elements, and each element can be selected at most once. Return these combinations in list form, where the list should not contain duplicate combinations.

Compared to the previous problem, the input array in this problem may contain duplicate elements, which introduces new challenges. For example, given array \\([4, \\hat{4}, 5]\\) and target element \\(9\\), the output of the existing code is \\([4, 5], [\\hat{4}, 5]\\), which contains duplicate subsets.

The reason for this duplication is that equal elements are selected multiple times in a certain round. In Figure 13-13, the first round has three choices, two of which are \\(4\\), creating two duplicate search branches that output duplicate subsets. Similarly, the two \\(4\\)'s in the second round also produce duplicate subsets.

Figure 13-13   Duplicate subsets caused by equal elements

","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1-pruning-equal-elements","level":3,"title":"1.   Pruning Equal Elements","text":"

To solve this problem, we need to limit equal elements to be selected only once in each round. The implementation is quite clever: since the array is already sorted, equal elements are adjacent. This means that in a certain round of selection, if the current element equals the element to its left, it means this element has already been selected, so we skip the current element directly.

At the same time, this problem specifies that each array element can only be selected once. Fortunately, we can also use the variable start to satisfy this constraint: after making choice \\(x_{i}\\), set the next round to start traversal from index \\(i + 1\\) onwards. This both eliminates duplicate subsets and avoids selecting elements multiple times.

","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_ii.py
def backtrack(\n    state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]\n):\n    \"\"\"Backtracking algorithm: Subset sum II\"\"\"\n    # When the subset sum equals target, record the solution\n    if target == 0:\n        res.append(list(state))\n        return\n    # Traverse all choices\n    # Pruning 2: start traversing from start to avoid generating duplicate subsets\n    # Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for i in range(start, len(choices)):\n        # Pruning 1: if the subset sum exceeds target, end the loop directly\n        # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0:\n            break\n        # Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if i > start and choices[i] == choices[i - 1]:\n            continue\n        # Attempt: make choice, update target, start\n        state.append(choices[i])\n        # Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res)\n        # Backtrack: undo choice, restore to previous state\n        state.pop()\n\ndef subset_sum_ii(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum II\"\"\"\n    state = []  # State (subset)\n    nums.sort()  # Sort nums\n    start = 0  # Start point for traversal\n    res = []  # Result list (subset list)\n    backtrack(state, target, nums, start, res)\n    return res\n
subset_sum_ii.cpp
/* Backtracking algorithm: Subset sum II */\nvoid backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.push_back(state);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (int i = start; i < choices.size(); i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.push_back(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum II */\nvector<vector<int>> subsetSumII(vector<int> &nums, int target) {\n    vector<int> state;              // State (subset)\n    sort(nums.begin(), nums.end()); // Sort nums\n    int start = 0;                  // Start point for traversal\n    vector<vector<int>> res;        // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_ii.java
/* Backtracking algorithm: Subset sum II */\nvoid backtrack(List<Integer> state, int target, int[] choices, int start, List<List<Integer>> res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (int i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.add(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum II */\nList<List<Integer>> subsetSumII(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    Arrays.sort(nums); // Sort nums\n    int start = 0; // Start point for traversal\n    List<List<Integer>> res = new ArrayList<>(); // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_ii.cs
/* Backtracking algorithm: Subset sum II */\nvoid Backtrack(List<int> state, int target, int[] choices, int start, List<List<int>> res) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (int i = start; i < choices.Length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.Add(choices[i]);\n        // Proceed to the next round of selection\n        Backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum II */\nList<List<int>> SubsetSumII(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    Array.Sort(nums); // Sort nums\n    int start = 0; // Start point for traversal\n    List<List<int>> res = []; // Result list (subset list)\n    Backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_ii.go
/* Backtracking algorithm: Subset sum II */\nfunc backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for i := start; i < len(*choices); i++ {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target-(*choices)[i] < 0 {\n            break\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if i > start && (*choices)[i] == (*choices)[i-1] {\n            continue\n        }\n        // Attempt: make choice, update target, start\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next round of selection\n        backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res)\n        // Backtrack: undo choice, restore to previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum II */\nfunc subsetSumII(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    sort.Ints(nums)         // Sort nums\n    start := 0              // Start point for traversal\n    res := make([][]int, 0) // Result list (subset list)\n    backtrackSubsetSumII(start, target, &state, &nums, &res)\n    return res\n}\n
subset_sum_ii.swift
/* Backtracking algorithm: Subset sum II */\nfunc backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        res.append(state)\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for i in choices.indices.dropFirst(start) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0 {\n            break\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if i > start, choices[i] == choices[i - 1] {\n            continue\n        }\n        // Attempt: make choice, update target, start\n        state.append(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum II */\nfunc subsetSumII(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let nums = nums.sorted() // Sort nums\n    let start = 0 // Start point for traversal\n    var res: [[Int]] = [] // Result list (subset list)\n    backtrack(state: &state, target: target, choices: nums, start: start, res: &res)\n    return res\n}\n
subset_sum_ii.js
/* Backtracking algorithm: Subset sum II */\nfunction backtrack(state, target, choices, start, res) {\n    // When the subset sum equals target, record the solution\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] === choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfunction subsetSumII(nums, target) {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Start point for traversal\n    const res = []; // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_ii.ts
/* Backtracking algorithm: Subset sum II */\nfunction backtrack(\n    state: number[],\n    target: number,\n    choices: number[],\n    start: number,\n    res: number[][]\n): void {\n    // When the subset sum equals target, record the solution\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] === choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfunction subsetSumII(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Start point for traversal\n    const res = []; // Result list (subset list)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
subset_sum_ii.dart
/* Backtracking algorithm: Subset sum II */\nvoid backtrack(\n  List<int> state,\n  int target,\n  List<int> choices,\n  int start,\n  List<List<int>> res,\n) {\n  // When the subset sum equals target, record the solution\n  if (target == 0) {\n    res.add(List.from(state));\n    return;\n  }\n  // Traverse all choices\n  // Pruning 2: start traversing from start to avoid generating duplicate subsets\n  // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n  for (int i = start; i < choices.length; i++) {\n    // Pruning 1: if the subset sum exceeds target, end the loop directly\n    // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n    if (target - choices[i] < 0) {\n      break;\n    }\n    // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n    if (i > start && choices[i] == choices[i - 1]) {\n      continue;\n    }\n    // Attempt: make choice, update target, start\n    state.add(choices[i]);\n    // Proceed to the next round of selection\n    backtrack(state, target - choices[i], choices, i + 1, res);\n    // Backtrack: undo choice, restore to previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum II */\nList<List<int>> subsetSumII(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  nums.sort(); // Sort nums\n  int start = 0; // Start point for traversal\n  List<List<int>> res = []; // Result list (subset list)\n  backtrack(state, target, nums, start, res);\n  return res;\n}\n
subset_sum_ii.rs
/* Backtracking algorithm: Subset sum II */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    choices: &[i32],\n    start: usize,\n    res: &mut Vec<Vec<i32>>,\n) {\n    // When the subset sum equals target, record the solution\n    if target == 0 {\n        res.push(state.clone());\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for i in start..choices.len() {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if target - choices[i] < 0 {\n            break;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if i > start && choices[i] == choices[i - 1] {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state.push(choices[i]);\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo choice, restore to previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    nums.sort(); // Sort nums\n    let start = 0; // Start point for traversal\n    let mut res = Vec::new(); // Result list (subset list)\n    backtrack(&mut state, target, nums, start, &mut res);\n    res\n}\n
subset_sum_ii.c
/* Backtracking algorithm: Subset sum II */\nvoid backtrack(int target, int *choices, int choicesSize, int start) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        for (int i = 0; i < stateSize; i++) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (int i = start; i < choicesSize; i++) {\n        // Pruning 1: Skip if subset sum exceeds target\n        if (target - choices[i] < 0) {\n            continue;\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Attempt: make choice, update target, start\n        state[stateSize] = choices[i];\n        stateSize++;\n        // Proceed to the next round of selection\n        backtrack(target - choices[i], choices, choicesSize, i + 1);\n        // Backtrack: undo choice, restore to previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum II */\nvoid subsetSumII(int *nums, int numsSize, int target) {\n    // Sort nums\n    qsort(nums, numsSize, sizeof(int), cmp);\n    // Start backtracking\n    backtrack(target, nums, numsSize, 0);\n}\n
subset_sum_ii.kt
/* Backtracking algorithm: Subset sum II */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    choices: IntArray,\n    start: Int,\n    res: MutableList<MutableList<Int>?>\n) {\n    // When the subset sum equals target, record the solution\n    if (target == 0) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Traverse all choices\n    // Pruning 2: start traversing from start to avoid generating duplicate subsets\n    // Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n    for (i in start..<choices.size) {\n        // Pruning 1: if the subset sum exceeds target, end the loop directly\n        // This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n        if (target - choices[i] < 0) {\n            break\n        }\n        // Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue\n        }\n        // Attempt: make choice, update target, start\n        state.add(choices[i])\n        // Proceed to the next round of selection\n        backtrack(state, target - choices[i], choices, i + 1, res)\n        // Backtrack: undo choice, restore to previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum II */\nfun subsetSumII(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    nums.sort() // Sort nums\n    val start = 0 // Start point for traversal\n    val res = mutableListOf<MutableList<Int>?>() // Result list (subset list)\n    backtrack(state, target, nums, start, res)\n    return res\n}\n
subset_sum_ii.rb
### Backtracking: subset sum II ###\ndef backtrack(state, target, choices, start, res)\n  # When the subset sum equals target, record the solution\n  if target.zero?\n    res << state.dup\n    return\n  end\n\n  # Traverse all choices\n  # Pruning 2: start traversing from start to avoid generating duplicate subsets\n  # Pruning 3: start traversing from start to avoid repeatedly selecting the same element\n  for i in start...choices.length\n    # Pruning 1: if the subset sum exceeds target, end the loop directly\n    # This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target\n    break if target - choices[i] < 0\n    # Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly\n    next if i > start && choices[i] == choices[i - 1]\n    # Attempt: make choice, update target, start\n    state << choices[i]\n    # Proceed to the next round of selection\n    backtrack(state, target - choices[i], choices, i + 1, res)\n    # Backtrack: undo choice, restore to previous state\n    state.pop\n  end\nend\n\n### Solve subset sum II ###\ndef subset_sum_ii(nums, target)\n  state = [] # State (subset)\n  nums.sort! # Sort nums\n  start = 0 # Start point for traversal\n  res = [] # Result list (subset list)\n  backtrack(state, target, nums, start, res)\n  res\nend\n

Figure 13-14 shows the backtracking process for array \\([4, 4, 5]\\) and target element \\(9\\), which includes four types of pruning operations. Combine the illustration with the code comments to understand the entire search process and how each pruning operation works.

Figure 13-14   Subset-sum II backtracking process

","path":["Chapter 13. Backtracking","13.3   Subset-Sum Problem"],"tags":[]},{"location":"chapter_backtracking/summary/","level":1,"title":"13.5   Summary","text":"","path":["Chapter 13. Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_backtracking/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • The backtracking algorithm is fundamentally an exhaustive search method. It finds solutions that meet specified conditions by performing a depth-first traversal of the solution space. During the search process, when a solution satisfying the conditions is found, it is recorded. The search ends either after finding all solutions or when the traversal is complete.
  • The backtracking algorithm search process consists of two parts: attempting and backtracking. It tries various choices through depth-first search. When encountering situations that violate constraints, it reverts the previous choice, returns to the previous state, and continues exploring other options. Attempting and backtracking are operations in opposite directions.
  • Backtracking problems typically contain multiple constraints, which can be utilized to implement pruning operations. Pruning can terminate unnecessary search branches early, significantly improving search efficiency.
  • The backtracking algorithm is primarily used to solve search problems and constraint satisfaction problems. While combinatorial optimization problems can be solved with backtracking, there are often more efficient or better-performing solutions available.
  • The permutation problem aims to find all possible permutations of elements in a given set. We use an array to record whether each element has been selected, thereby pruning search branches that attempt to select the same element repeatedly, ensuring each element is selected exactly once.
  • In the permutation problem, if the set contains duplicate elements, the final result will contain duplicate permutations. We need to impose a constraint so that equal elements can only be selected once per round, which is typically achieved using a hash set.
  • The subset-sum problem aims to find all subsets of a given set that sum to a target value. Since the set is unordered but the search process outputs results in all orders, duplicate subsets are generated. We sort the data before backtracking and use a variable to indicate the starting point of each round's traversal, thereby pruning search branches that generate duplicate subsets.
  • For the subset-sum problem, equal elements in the array produce duplicate sets. We leverage the precondition that the array is sorted by checking whether adjacent elements are equal to implement pruning, ensuring that equal elements can only be selected once per round.
  • The \\(n\\) queens problem aims to find placements of \\(n\\) queens on an \\(n \\times n\\) chessboard such that no two queens can attack each other. The constraints of this problem include row constraints, column constraints, and main and anti-diagonal constraints. To satisfy row constraints, we adopt a row-by-row placement strategy, ensuring exactly one queen is placed in each row.
  • The handling of column constraints and diagonal constraints is similar. For column constraints, we use an array to record whether each column has a queen, thereby indicating whether a selected cell is valid. For diagonal constraints, we use two arrays to separately record whether queens exist on each main or anti-diagonal. The challenge lies in finding the row-column index pattern that characterizes cells on the same main (anti-)diagonal.
","path":["Chapter 13. Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_backtracking/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: How should we understand the relationship between backtracking and recursion?

Overall, backtracking is an \"algorithm strategy\", while recursion is more like a \"tool\".

  • The backtracking algorithm is typically implemented based on recursion. However, backtracking is one application scenario of recursion and represents the application of recursion in search problems.
  • The structure of recursion embodies the \"subproblem decomposition\" problem-solving paradigm, commonly used to solve problems involving divide-and-conquer, backtracking, and dynamic programming (memoized recursion).
","path":["Chapter 13. Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/","level":1,"title":"Chapter 2.   Complexity Analysis","text":"

Abstract

Complexity analysis is like a space-time guide in the vast universe of algorithms.

It leads us to explore deeply within the two dimensions of time and space, seeking more elegant solutions.

","path":["Chapter 2. Complexity Analysis","Chapter 2.   Complexity Analysis"],"tags":[]},{"location":"chapter_computational_complexity/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 2.1   Algorithm Efficiency Evaluation
  • 2.2   Iteration and Recursion
  • 2.3   Time Complexity
  • 2.4   Space Complexity
  • 2.5   Summary
","path":["Chapter 2. Complexity Analysis","Chapter 2.   Complexity Analysis"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/","level":1,"title":"2.2   Iteration and Recursion","text":"

In algorithms, repeatedly executing a task is very common and closely related to complexity analysis. Therefore, before introducing time complexity and space complexity, let's first understand how to implement repeated task execution in programs, namely the two basic program control structures: iteration and recursion.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#221-iteration","level":2,"title":"2.2.1   Iteration","text":"

Iteration is a control structure for repeatedly executing a task. In iteration, a program repeatedly executes a segment of code under certain conditions until those conditions are no longer satisfied.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#1-for-loop","level":3,"title":"1.   For Loop","text":"

The for loop is one of the most common forms of iteration, suitable for use when the number of iterations is known in advance.

The following function implements the summation \\(1 + 2 + \\dots + n\\) based on a for loop, with the sum result recorded using the variable res. Note that in Python, range(a, b) corresponds to a \"left-closed, right-open\" interval, with the traversal range being \\(a, a + 1, \\dots, b-1\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
def for_loop(n: int) -> int:\n    \"\"\"for loop\"\"\"\n    res = 0\n    # Sum 1, 2, ..., n-1, n\n    for i in range(1, n + 1):\n        res += i\n    return res\n
iteration.cpp
/* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; ++i) {\n        res += i;\n    }\n    return res;\n}\n
iteration.java
/* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
iteration.cs
/* for loop */\nint ForLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
iteration.go
/* for loop */\nfunc forLoop(n int) int {\n    res := 0\n    // Sum 1, 2, ..., n-1, n\n    for i := 1; i <= n; i++ {\n        res += i\n    }\n    return res\n}\n
iteration.swift
/* for loop */\nfunc forLoop(n: Int) -> Int {\n    var res = 0\n    // Sum 1, 2, ..., n-1, n\n    for i in 1 ... n {\n        res += i\n    }\n    return res\n}\n
iteration.js
/* for loop */\nfunction forLoop(n) {\n    let res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
iteration.ts
/* for loop */\nfunction forLoop(n: number): number {\n    let res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
iteration.dart
/* for loop */\nint forLoop(int n) {\n  int res = 0;\n  // Sum 1, 2, ..., n-1, n\n  for (int i = 1; i <= n; i++) {\n    res += i;\n  }\n  return res;\n}\n
iteration.rs
/* for loop */\nfn for_loop(n: i32) -> i32 {\n    let mut res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for i in 1..=n {\n        res += i;\n    }\n    res\n}\n
iteration.c
/* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
iteration.kt
/* for loop */\nfun forLoop(n: Int): Int {\n    var res = 0\n    // Sum 1, 2, ..., n-1, n\n    for (i in 1..n) {\n        res += i\n    }\n    return res\n}\n
iteration.rb
### for loop ###\ndef for_loop(n)\n  res = 0\n\n  # Sum 1, 2, ..., n-1, n\n  for i in 1..n\n    res += i\n  end\n\n  res\nend\n

Figure 2-1 shows the flowchart of this summation function.

Figure 2-1   Flowchart of the summation function

The number of operations in this summation function is proportional to the input data size \\(n\\), or has a \"linear relationship\". In fact, time complexity describes precisely this \"linear relationship\". Related content will be introduced in detail in the next section.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-while-loop","level":3,"title":"2.   While Loop","text":"

Similar to the for loop, the while loop is also a method for implementing iteration. In a while loop, the program first checks the condition in each round; if the condition is true, it continues execution, otherwise it ends the loop.

Below we use a while loop to implement the summation \\(1 + 2 + \\dots + n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
def while_loop(n: int) -> int:\n    \"\"\"while loop\"\"\"\n    res = 0\n    i = 1  # Initialize condition variable\n    # Sum 1, 2, ..., n-1, n\n    while i <= n:\n        res += i\n        i += 1  # Update condition variable\n    return res\n
iteration.cpp
/* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i++; // Update condition variable\n    }\n    return res;\n}\n
iteration.java
/* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i++; // Update condition variable\n    }\n    return res;\n}\n
iteration.cs
/* while loop */\nint WhileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i += 1; // Update condition variable\n    }\n    return res;\n}\n
iteration.go
/* while loop */\nfunc whileLoop(n int) int {\n    res := 0\n    // Initialize condition variable\n    i := 1\n    // Sum 1, 2, ..., n-1, n\n    for i <= n {\n        res += i\n        // Update condition variable\n        i++\n    }\n    return res\n}\n
iteration.swift
/* while loop */\nfunc whileLoop(n: Int) -> Int {\n    var res = 0\n    var i = 1 // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while i <= n {\n        res += i\n        i += 1 // Update condition variable\n    }\n    return res\n}\n
iteration.js
/* while loop */\nfunction whileLoop(n) {\n    let res = 0;\n    let i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i++; // Update condition variable\n    }\n    return res;\n}\n
iteration.ts
/* while loop */\nfunction whileLoop(n: number): number {\n    let res = 0;\n    let i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i++; // Update condition variable\n    }\n    return res;\n}\n
iteration.dart
/* while loop */\nint whileLoop(int n) {\n  int res = 0;\n  int i = 1; // Initialize condition variable\n  // Sum 1, 2, ..., n-1, n\n  while (i <= n) {\n    res += i;\n    i++; // Update condition variable\n  }\n  return res;\n}\n
iteration.rs
/* while loop */\nfn while_loop(n: i32) -> i32 {\n    let mut res = 0;\n    let mut i = 1; // Initialize condition variable\n\n    // Sum 1, 2, ..., n-1, n\n    while i <= n {\n        res += i;\n        i += 1; // Update condition variable\n    }\n    res\n}\n
iteration.c
/* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i;\n        i++; // Update condition variable\n    }\n    return res;\n}\n
iteration.kt
/* while loop */\nfun whileLoop(n: Int): Int {\n    var res = 0\n    var i = 1 // Initialize condition variable\n    // Sum 1, 2, ..., n-1, n\n    while (i <= n) {\n        res += i\n        i++ // Update condition variable\n    }\n    return res\n}\n
iteration.rb
### while loop ###\ndef while_loop(n)\n  res = 0\n  i = 1 # Initialize condition variable\n\n  # Sum 1, 2, ..., n-1, n\n  while i <= n\n    res += i\n    i += 1 # Update condition variable\n  end\n\n  res\nend\n

The while loop has greater flexibility than the for loop. In a while loop, we can freely design the initialization and update steps of the condition variable.

For example, in the following code, the condition variable \\(i\\) is updated twice per round, which is not convenient to implement using a for loop:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
def while_loop_ii(n: int) -> int:\n    \"\"\"while loop (two updates)\"\"\"\n    res = 0\n    i = 1  # Initialize condition variable\n    # Sum 1, 4, 10, ...\n    while i <= n:\n        res += i\n        # Update condition variable\n        i += 1\n        i *= 2\n    return res\n
iteration.cpp
/* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
iteration.java
/* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
iteration.cs
/* while loop (two updates) */\nint WhileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i += 1; \n        i *= 2;\n    }\n    return res;\n}\n
iteration.go
/* while loop (two updates) */\nfunc whileLoopII(n int) int {\n    res := 0\n    // Initialize condition variable\n    i := 1\n    // Sum 1, 4, 10, ...\n    for i <= n {\n        res += i\n        // Update condition variable\n        i++\n        i *= 2\n    }\n    return res\n}\n
iteration.swift
/* while loop (two updates) */\nfunc whileLoopII(n: Int) -> Int {\n    var res = 0\n    var i = 1 // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while i <= n {\n        res += i\n        // Update condition variable\n        i += 1\n        i *= 2\n    }\n    return res\n}\n
iteration.js
/* while loop (two updates) */\nfunction whileLoopII(n) {\n    let res = 0;\n    let i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
iteration.ts
/* while loop (two updates) */\nfunction whileLoopII(n: number): number {\n    let res = 0;\n    let i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
iteration.dart
/* while loop (two updates) */\nint whileLoopII(int n) {\n  int res = 0;\n  int i = 1; // Initialize condition variable\n  // Sum 1, 4, 10, ...\n  while (i <= n) {\n    res += i;\n    // Update condition variable\n    i++;\n    i *= 2;\n  }\n  return res;\n}\n
iteration.rs
/* while loop (two updates) */\nfn while_loop_ii(n: i32) -> i32 {\n    let mut res = 0;\n    let mut i = 1; // Initialize condition variable\n\n    // Sum 1, 4, 10, ...\n    while i <= n {\n        res += i;\n        // Update condition variable\n        i += 1;\n        i *= 2;\n    }\n    res\n}\n
iteration.c
/* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i;\n        // Update condition variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
iteration.kt
/* while loop (two updates) */\nfun whileLoopII(n: Int): Int {\n    var res = 0\n    var i = 1 // Initialize condition variable\n    // Sum 1, 4, 10, ...\n    while (i <= n) {\n        res += i\n        // Update condition variable\n        i++\n        i *= 2\n    }\n    return res\n}\n
iteration.rb
### while loop (two updates) ###\ndef while_loop_ii(n)\n  res = 0\n  i = 1 # Initialize condition variable\n\n  # Sum 1, 4, 10, ...\n  while i <= n\n    res += i\n    # Update condition variable\n    i += 1\n    i *= 2\n  end\n\n  res\nend\n

Overall, for loops have more compact code, while while loops are more flexible; both can implement iterative structures. The choice of which to use should be determined based on the requirements of the specific problem.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#3-nested-loops","level":3,"title":"3.   Nested Loops","text":"

We can nest one loop structure inside another. Below is an example using for loops:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
def nested_for_loop(n: int) -> str:\n    \"\"\"Nested for loop\"\"\"\n    res = \"\"\n    # Loop i = 1, 2, ..., n-1, n\n    for i in range(1, n + 1):\n        # Loop j = 1, 2, ..., n-1, n\n        for j in range(1, n + 1):\n            res += f\"({i}, {j}), \"\n    return res\n
iteration.cpp
/* Nested for loop */\nstring nestedForLoop(int n) {\n    ostringstream res;\n    // Loop i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; ++i) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; ++j) {\n            res << \"(\" << i << \", \" << j << \"), \";\n        }\n    }\n    return res.str();\n}\n
iteration.java
/* Nested for loop */\nString nestedForLoop(int n) {\n    StringBuilder res = new StringBuilder();\n    // Loop i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            res.append(\"(\" + i + \", \" + j + \"), \");\n        }\n    }\n    return res.toString();\n}\n
iteration.cs
/* Nested for loop */\nstring NestedForLoop(int n) {\n    StringBuilder res = new();\n    // Loop i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            res.Append($\"({i}, {j}), \");\n        }\n    }\n    return res.ToString();\n}\n
iteration.go
/* Nested for loop */\nfunc nestedForLoop(n int) string {\n    res := \"\"\n    // Loop i = 1, 2, ..., n-1, n\n    for i := 1; i <= n; i++ {\n        for j := 1; j <= n; j++ {\n            // Loop j = 1, 2, ..., n-1, n\n            res += fmt.Sprintf(\"(%d, %d), \", i, j)\n        }\n    }\n    return res\n}\n
iteration.swift
/* Nested for loop */\nfunc nestedForLoop(n: Int) -> String {\n    var res = \"\"\n    // Loop i = 1, 2, ..., n-1, n\n    for i in 1 ... n {\n        // Loop j = 1, 2, ..., n-1, n\n        for j in 1 ... n {\n            res.append(\"(\\(i), \\(j)), \")\n        }\n    }\n    return res\n}\n
iteration.js
/* Nested for loop */\nfunction nestedForLoop(n) {\n    let res = '';\n    // Loop i = 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (let j = 1; j <= n; j++) {\n            res += `(${i}, ${j}), `;\n        }\n    }\n    return res;\n}\n
iteration.ts
/* Nested for loop */\nfunction nestedForLoop(n: number): string {\n    let res = '';\n    // Loop i = 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (let j = 1; j <= n; j++) {\n            res += `(${i}, ${j}), `;\n        }\n    }\n    return res;\n}\n
iteration.dart
/* Nested for loop */\nString nestedForLoop(int n) {\n  String res = \"\";\n  // Loop i = 1, 2, ..., n-1, n\n  for (int i = 1; i <= n; i++) {\n    // Loop j = 1, 2, ..., n-1, n\n    for (int j = 1; j <= n; j++) {\n      res += \"($i, $j), \";\n    }\n  }\n  return res;\n}\n
iteration.rs
/* Nested for loop */\nfn nested_for_loop(n: i32) -> String {\n    let mut res = vec![];\n    // Loop i = 1, 2, ..., n-1, n\n    for i in 1..=n {\n        // Loop j = 1, 2, ..., n-1, n\n        for j in 1..=n {\n            res.push(format!(\"({}, {}), \", i, j));\n        }\n    }\n    res.join(\"\")\n}\n
iteration.c
/* Nested for loop */\nchar *nestedForLoop(int n) {\n    // n * n is the number of points, \"(i, j), \" string max length is 6+10*2, plus extra space for null character \\0\n    int size = n * n * 26 + 1;\n    char *res = malloc(size * sizeof(char));\n    // Loop i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            char tmp[26];\n            snprintf(tmp, sizeof(tmp), \"(%d, %d), \", i, j);\n            strncat(res, tmp, size - strlen(res) - 1);\n        }\n    }\n    return res;\n}\n
iteration.kt
/* Nested for loop */\nfun nestedForLoop(n: Int): String {\n    val res = StringBuilder()\n    // Loop i = 1, 2, ..., n-1, n\n    for (i in 1..n) {\n        // Loop j = 1, 2, ..., n-1, n\n        for (j in 1..n) {\n            res.append(\" ($i, $j), \")\n        }\n    }\n    return res.toString()\n}\n
iteration.rb
### Nested for loop ###\ndef nested_for_loop(n)\n  res = \"\"\n\n  # Loop i = 1, 2, ..., n-1, n\n  for i in 1..n\n    # Loop j = 1, 2, ..., n-1, n\n    for j in 1..n\n      res += \"(#{i}, #{j}), \"\n    end\n  end\n\n  res\nend\n

Figure 2-2 shows the flowchart of this nested loop.

Figure 2-2   Flowchart of nested loops

In this case, the number of operations of the function is proportional to \\(n^2\\), or the algorithm's running time has a \"quadratic relationship\" with the input data size \\(n\\).

We can continue adding nested loops, where each nesting is a \"dimension increase\", raising the time complexity to \"cubic relationship\", \"quartic relationship\", and so on.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#222-recursion","level":2,"title":"2.2.2   Recursion","text":"

Recursion is an algorithmic strategy that solves problems by having a function call itself. It mainly consists of two phases.

  1. Descend: The program continuously calls itself deeper, usually passing in smaller or more simplified parameters, until reaching a \"termination condition\".
  2. Ascend: After triggering the \"termination condition\", the program returns layer by layer from the deepest recursive function, aggregating the result of each layer.

From an implementation perspective, recursive code mainly consists of three elements.

  1. Termination condition: Used to determine when to switch from \"descending\" to \"ascending\".
  2. Recursive call: Corresponds to \"descending\", where the function calls itself, usually with smaller or more simplified parameters.
  3. Return result: Corresponds to \"ascending\", returning the result of the current recursion level to the previous layer.

Observe the following code. We only need to call the function recur(n) to complete the calculation of \\(1 + 2 + \\dots + n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
def recur(n: int) -> int:\n    \"\"\"Recursion\"\"\"\n    # Termination condition\n    if n == 1:\n        return 1\n    # Recurse: recursive call\n    res = recur(n - 1)\n    # Return: return result\n    return n + res\n
recursion.cpp
/* Recursion */\nint recur(int n) {\n    // Termination condition\n    if (n == 1)\n        return 1;\n    // Recurse: recursive call\n    int res = recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.java
/* Recursion */\nint recur(int n) {\n    // Termination condition\n    if (n == 1)\n        return 1;\n    // Recurse: recursive call\n    int res = recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.cs
/* Recursion */\nint Recur(int n) {\n    // Termination condition\n    if (n == 1)\n        return 1;\n    // Recurse: recursive call\n    int res = Recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.go
/* Recursion */\nfunc recur(n int) int {\n    // Termination condition\n    if n == 1 {\n        return 1\n    }\n    // Recurse: recursive call\n    res := recur(n - 1)\n    // Return: return result\n    return n + res\n}\n
recursion.swift
/* Recursion */\nfunc recur(n: Int) -> Int {\n    // Termination condition\n    if n == 1 {\n        return 1\n    }\n    // Recurse: recursive call\n    let res = recur(n: n - 1)\n    // Return: return result\n    return n + res\n}\n
recursion.js
/* Recursion */\nfunction recur(n) {\n    // Termination condition\n    if (n === 1) return 1;\n    // Recurse: recursive call\n    const res = recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.ts
/* Recursion */\nfunction recur(n: number): number {\n    // Termination condition\n    if (n === 1) return 1;\n    // Recurse: recursive call\n    const res = recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.dart
/* Recursion */\nint recur(int n) {\n  // Termination condition\n  if (n == 1) return 1;\n  // Recurse: recursive call\n  int res = recur(n - 1);\n  // Return: return result\n  return n + res;\n}\n
recursion.rs
/* Recursion */\nfn recur(n: i32) -> i32 {\n    // Termination condition\n    if n == 1 {\n        return 1;\n    }\n    // Recurse: recursive call\n    let res = recur(n - 1);\n    // Return: return result\n    n + res\n}\n
recursion.c
/* Recursion */\nint recur(int n) {\n    // Termination condition\n    if (n == 1)\n        return 1;\n    // Recurse: recursive call\n    int res = recur(n - 1);\n    // Return: return result\n    return n + res;\n}\n
recursion.kt
/* Recursion */\nfun recur(n: Int): Int {\n    // Termination condition\n    if (n == 1)\n        return 1\n    // Descend: recursive call\n    val res = recur(n - 1)\n    // Return: return result\n    return n + res\n}\n
recursion.rb
### Recursion ###\ndef recur(n)\n  # Termination condition\n  return 1 if n == 1\n  # Recurse: recursive call\n  res = recur(n - 1)\n  # Return: return result\n  n + res\nend\n

Figure 2-3 shows the recursive process of this function.

Figure 2-3   Recursive process of the summation function

Although from a computational perspective, iteration and recursion can achieve the same results, they represent two completely different paradigms for thinking about and solving problems.

  • Iteration: Solves problems \"bottom-up\". Starting from the most basic steps, these steps are then repeatedly executed or accumulated until the task is complete.
  • Recursion: Solves problems \"top-down\". The original problem is decomposed into smaller subproblems that have the same form as the original problem. These subproblems continue to be decomposed into even smaller subproblems until reaching the base case (where the solution is known).

Taking the above summation function as an example, let the problem be \\(f(n) = 1 + 2 + \\dots + n\\).

  • Iteration: Simulates the summation process in a loop, traversing from \\(1\\) to \\(n\\), performing the summation operation in each round to obtain \\(f(n)\\).
  • Recursion: Decomposes the problem into the subproblem \\(f(n) = n + f(n-1)\\), continuously decomposing (recursively) until terminating at the base case \\(f(1) = 1\\).
","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#1-call-stack","level":3,"title":"1.   Call Stack","text":"

Each time a recursive function calls itself, the system allocates memory for the newly opened function to store local variables, call addresses, and other information. This leads to two consequences.

  • The function's context data is stored in a memory area called \"stack frame space\", which is not released until the function returns. Therefore, recursion usually consumes more memory space than iteration.
  • Recursive function calls incur additional overhead. Therefore, recursion is usually less time-efficient than loops.

As shown in Figure 2-4, before the termination condition is triggered, there are \\(n\\) unreturned recursive functions existing simultaneously, with a recursion depth of \\(n\\).

Figure 2-4   Recursion call depth

In practice, the recursion depth allowed by programming languages is usually limited, and excessively deep recursion may lead to stack overflow errors.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-tail-recursion","level":3,"title":"2.   Tail Recursion","text":"

Interestingly, if a function makes the recursive call as the very last step before returning, the function can be optimized by the compiler or interpreter to have space efficiency comparable to iteration. This case is called tail recursion.

  • Regular recursion: When a function returns to the previous level, it needs to continue executing code, so the system needs to save the context of the previous layer's call.
  • Tail recursion: The recursive call is the last operation before the function returns, meaning that after returning to the previous level, there is no need to continue executing other operations, so the system does not need to save the context of the previous layer's function.

Taking the calculation of \\(1 + 2 + \\dots + n\\) as an example, we can set the result variable res as a function parameter to implement tail recursion:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
def tail_recur(n, res):\n    \"\"\"Tail recursion\"\"\"\n    # Termination condition\n    if n == 0:\n        return res\n    # Tail recursive call\n    return tail_recur(n - 1, res + n)\n
recursion.cpp
/* Tail recursion */\nint tailRecur(int n, int res) {\n    // Termination condition\n    if (n == 0)\n        return res;\n    // Tail recursive call\n    return tailRecur(n - 1, res + n);\n}\n
recursion.java
/* Tail recursion */\nint tailRecur(int n, int res) {\n    // Termination condition\n    if (n == 0)\n        return res;\n    // Tail recursive call\n    return tailRecur(n - 1, res + n);\n}\n
recursion.cs
/* Tail recursion */\nint TailRecur(int n, int res) {\n    // Termination condition\n    if (n == 0)\n        return res;\n    // Tail recursive call\n    return TailRecur(n - 1, res + n);\n}\n
recursion.go
/* Tail recursion */\nfunc tailRecur(n int, res int) int {\n    // Termination condition\n    if n == 0 {\n        return res\n    }\n    // Tail recursive call\n    return tailRecur(n-1, res+n)\n}\n
recursion.swift
/* Tail recursion */\nfunc tailRecur(n: Int, res: Int) -> Int {\n    // Termination condition\n    if n == 0 {\n        return res\n    }\n    // Tail recursive call\n    return tailRecur(n: n - 1, res: res + n)\n}\n
recursion.js
/* Tail recursion */\nfunction tailRecur(n, res) {\n    // Termination condition\n    if (n === 0) return res;\n    // Tail recursive call\n    return tailRecur(n - 1, res + n);\n}\n
recursion.ts
/* Tail recursion */\nfunction tailRecur(n: number, res: number): number {\n    // Termination condition\n    if (n === 0) return res;\n    // Tail recursive call\n    return tailRecur(n - 1, res + n);\n}\n
recursion.dart
/* Tail recursion */\nint tailRecur(int n, int res) {\n  // Termination condition\n  if (n == 0) return res;\n  // Tail recursive call\n  return tailRecur(n - 1, res + n);\n}\n
recursion.rs
/* Tail recursion */\nfn tail_recur(n: i32, res: i32) -> i32 {\n    // Termination condition\n    if n == 0 {\n        return res;\n    }\n    // Tail recursive call\n    tail_recur(n - 1, res + n)\n}\n
recursion.c
/* Tail recursion */\nint tailRecur(int n, int res) {\n    // Termination condition\n    if (n == 0)\n        return res;\n    // Tail recursive call\n    return tailRecur(n - 1, res + n);\n}\n
recursion.kt
/* Tail recursion */\ntailrec fun tailRecur(n: Int, res: Int): Int {\n    // Add tailrec keyword to enable tail recursion optimization\n    // Termination condition\n    if (n == 0)\n        return res\n    // Tail recursive call\n    return tailRecur(n - 1, res + n)\n}\n
recursion.rb
### Tail recursion ###\ndef tail_recur(n, res)\n  # Termination condition\n  return res if n == 0\n  # Tail recursive call\n  tail_recur(n - 1, res + n)\nend\n

The execution process of tail recursion is shown in Figure 2-5. Comparing regular recursion and tail recursion, the execution point of the summation operation is different.

  • Regular recursion: The summation operation is performed during the \"ascending\" process, requiring an additional summation operation after each layer returns.
  • Tail recursion: The summation operation is performed during the \"descending\" process; the \"ascending\" process only needs to return layer by layer.

Figure 2-5   Tail recursion process

Tip

Please note that many compilers or interpreters do not support tail recursion optimization. For example, Python does not support tail recursion optimization by default, so even if a function is in tail recursive form, it may still encounter stack overflow issues.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#3-recursion-tree","level":3,"title":"3.   Recursion Tree","text":"

When dealing with algorithmic problems related to \"divide and conquer\", recursion often provides a more intuitive approach and more readable code than iteration. Taking the \"Fibonacci sequence\" as an example.

Question

Given a Fibonacci sequence \\(0, 1, 1, 2, 3, 5, 8, 13, \\dots\\), find the \\(n\\)-th number in the sequence.

Let the \\(n\\)-th number of the Fibonacci sequence be \\(f(n)\\). Two conclusions can be easily obtained.

  • The first two numbers of the sequence are \\(f(1) = 0\\) and \\(f(2) = 1\\).
  • Each number in the sequence is the sum of the previous two numbers, i.e., \\(f(n) = f(n - 1) + f(n - 2)\\).

Following the recurrence relation to make recursive calls, with the first two numbers as termination conditions, we can write the recursive code. Calling fib(n) will give us the \\(n\\)-th number of the Fibonacci sequence:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
def fib(n: int) -> int:\n    \"\"\"Fibonacci sequence: recursion\"\"\"\n    # Termination condition f(1) = 0, f(2) = 1\n    if n == 1 or n == 2:\n        return n - 1\n    # Recursive call f(n) = f(n-1) + f(n-2)\n    res = fib(n - 1) + fib(n - 2)\n    # Return result f(n)\n    return res\n
recursion.cpp
/* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.java
/* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.cs
/* Fibonacci sequence: recursion */\nint Fib(int n) {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    int res = Fib(n - 1) + Fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.go
/* Fibonacci sequence: recursion */\nfunc fib(n int) int {\n    // Termination condition f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1\n    }\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    res := fib(n-1) + fib(n-2)\n    // Return result f(n)\n    return res\n}\n
recursion.swift
/* Fibonacci sequence: recursion */\nfunc fib(n: Int) -> Int {\n    // Termination condition f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1\n    }\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    let res = fib(n: n - 1) + fib(n: n - 2)\n    // Return result f(n)\n    return res\n}\n
recursion.js
/* Fibonacci sequence: recursion */\nfunction fib(n) {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n === 1 || n === 2) return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    const res = fib(n - 1) + fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.ts
/* Fibonacci sequence: recursion */\nfunction fib(n: number): number {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n === 1 || n === 2) return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    const res = fib(n - 1) + fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.dart
/* Fibonacci sequence: recursion */\nint fib(int n) {\n  // Termination condition f(1) = 0, f(2) = 1\n  if (n == 1 || n == 2) return n - 1;\n  // Recursive call f(n) = f(n-1) + f(n-2)\n  int res = fib(n - 1) + fib(n - 2);\n  // Return result f(n)\n  return res;\n}\n
recursion.rs
/* Fibonacci sequence: recursion */\nfn fib(n: i32) -> i32 {\n    // Termination condition f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1;\n    }\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    let res = fib(n - 1) + fib(n - 2);\n    // Return result\n    res\n}\n
recursion.c
/* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return result f(n)\n    return res;\n}\n
recursion.kt
/* Fibonacci sequence: recursion */\nfun fib(n: Int): Int {\n    // Termination condition f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1\n    // Recursive call f(n) = f(n-1) + f(n-2)\n    val res = fib(n - 1) + fib(n - 2)\n    // Return result f(n)\n    return res\n}\n
recursion.rb
### Fibonacci sequence: recursion ###\ndef fib(n)\n  # Termination condition f(1) = 0, f(2) = 1\n  return n - 1 if n == 1 || n == 2\n  # Recursive call f(n) = f(n-1) + f(n-2)\n  res = fib(n - 1) + fib(n - 2)\n  # Return result f(n)\n  res\nend\n

Observing the above code, we recursively call two functions within the function, meaning that one call produces two call branches. As shown in Figure 2-6, such continuous recursive calling will eventually produce a recursion tree with \\(n\\) levels.

Figure 2-6   Recursion tree of the Fibonacci sequence

Fundamentally, recursion embodies the paradigm of \"decomposing a problem into smaller subproblems\", and this divide-and-conquer strategy is crucial.

  • From an algorithmic perspective, many important algorithmic strategies such as searching, sorting, backtracking, divide and conquer, and dynamic programming directly or indirectly apply this way of thinking.
  • From a data structure perspective, recursion is naturally suited for handling problems related to linked lists, trees, and graphs, because they are well-suited for analysis using divide-and-conquer thinking.
","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#223-comparison-of-the-two","level":2,"title":"2.2.3   Comparison of the Two","text":"

Summarizing the above content, as shown in Table 2-1, iteration and recursion differ in implementation, performance, and applicability.

Table 2-1   Comparison of iteration and recursion characteristics

Iteration Recursion Implementation Loop structure Function calls itself Time efficiency Generally more efficient, no function call overhead Each function call incurs overhead Memory usage Usually uses a fixed amount of memory space Accumulated function calls may use a large amount of stack frame space Suitable problems Suitable for simple loop tasks, with intuitive and readable code Suitable for subproblem decomposition, such as trees, graphs, divide and conquer, backtracking, etc., with concise and clear code structure

Tip

If you find the following content difficult to understand, you can review it after reading the \"Stack\" chapter.

What is the intrinsic relationship between iteration and recursion? Taking the above recursive function as an example, the summation operation is performed during the \"ascending\" phase of recursion. This means that the function called first actually completes its summation operation last, and this working mechanism is similar to the \"last-in, first-out\" principle of stacks.

In fact, recursive terminology such as \"call stack\" and \"stack frame space\" already hints at the close relationship between recursion and stacks.

  1. Descend: When a function is called, the system allocates a new stack frame on the \"call stack\" for that function to store the function's local variables, parameters, return address, and other data.
  2. Ascend: When the function completes execution and returns, the corresponding stack frame is removed from the \"call stack\", restoring the execution environment of the previous function.

Therefore, we can use an explicit stack to simulate the behavior of the call stack, thus transforming recursion into iterative form:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
def for_loop_recur(n: int) -> int:\n    \"\"\"Simulate recursion using iteration\"\"\"\n    # Use an explicit stack to simulate the system call stack\n    stack = []\n    res = 0\n    # Recurse: recursive call\n    for i in range(n, 0, -1):\n        # Simulate \"recurse\" with \"push\"\n        stack.append(i)\n    # Return: return result\n    while stack:\n        # Simulate \"return\" with \"pop\"\n        res += stack.pop()\n    # res = 1+2+3+...+n\n    return res\n
recursion.cpp
/* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    stack<int> stack;\n    int res = 0;\n    // Recurse: recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i);\n    }\n    // Return: return result\n    while (!stack.empty()) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.top();\n        stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.java
/* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    Stack<Integer> stack = new Stack<>();\n    int res = 0;\n    // Recurse: recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i);\n    }\n    // Return: return result\n    while (!stack.isEmpty()) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.cs
/* Simulate recursion using iteration */\nint ForLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    Stack<int> stack = new();\n    int res = 0;\n    // Recurse: recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack.Push(i);\n    }\n    // Return: return result\n    while (stack.Count > 0) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.Pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.go
/* Simulate recursion using iteration */\nfunc forLoopRecur(n int) int {\n    // Use an explicit stack to simulate the system call stack\n    stack := list.New()\n    res := 0\n    // Recurse: recursive call\n    for i := n; i > 0; i-- {\n        // Simulate \"recurse\" with \"push\"\n        stack.PushBack(i)\n    }\n    // Return: return result\n    for stack.Len() != 0 {\n        // Simulate \"return\" with \"pop\"\n        res += stack.Back().Value.(int)\n        stack.Remove(stack.Back())\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
recursion.swift
/* Simulate recursion using iteration */\nfunc forLoopRecur(n: Int) -> Int {\n    // Use an explicit stack to simulate the system call stack\n    var stack: [Int] = []\n    var res = 0\n    // Recurse: recursive call\n    for i in (1 ... n).reversed() {\n        // Simulate \"recurse\" with \"push\"\n        stack.append(i)\n    }\n    // Return: return result\n    while !stack.isEmpty {\n        // Simulate \"return\" with \"pop\"\n        res += stack.removeLast()\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
recursion.js
/* Simulate recursion using iteration */\nfunction forLoopRecur(n) {\n    // Use an explicit stack to simulate the system call stack\n    const stack = [];\n    let res = 0;\n    // Recurse: recursive call\n    for (let i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i);\n    }\n    // Return: return result\n    while (stack.length) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.ts
/* Simulate recursion using iteration */\nfunction forLoopRecur(n: number): number {\n    // Use an explicit stack to simulate the system call stack\n    const stack: number[] = [];\n    let res: number = 0;\n    // Recurse: recursive call\n    for (let i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i);\n    }\n    // Return: return result\n    while (stack.length) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.dart
/* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n  // Use an explicit stack to simulate the system call stack\n  List<int> stack = [];\n  int res = 0;\n  // Recurse: recursive call\n  for (int i = n; i > 0; i--) {\n    // Simulate \"recurse\" with \"push\"\n    stack.add(i);\n  }\n  // Return: return result\n  while (!stack.isEmpty) {\n    // Simulate \"return\" with \"pop\"\n    res += stack.removeLast();\n  }\n  // res = 1+2+3+...+n\n  return res;\n}\n
recursion.rs
/* Simulate recursion using iteration */\nfn for_loop_recur(n: i32) -> i32 {\n    // Use an explicit stack to simulate the system call stack\n    let mut stack = Vec::new();\n    let mut res = 0;\n    // Recurse: recursive call\n    for i in (1..=n).rev() {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i);\n    }\n    // Return: return result\n    while !stack.is_empty() {\n        // Simulate \"return\" with \"pop\"\n        res += stack.pop().unwrap();\n    }\n    // res = 1+2+3+...+n\n    res\n}\n
recursion.c
/* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    int stack[1000]; // Use a large array to simulate stack\n    int top = -1;    // Stack top index\n    int res = 0;\n    // Recurse: recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate \"recurse\" with \"push\"\n        stack[1 + top++] = i;\n    }\n    // Return: return result\n    while (top >= 0) {\n        // Simulate \"return\" with \"pop\"\n        res += stack[top--];\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
recursion.kt
/* Simulate recursion using iteration */\nfun forLoopRecur(n: Int): Int {\n    // Use an explicit stack to simulate the system call stack\n    val stack = Stack<Int>()\n    var res = 0\n    // Descend: recursive call\n    for (i in n downTo 0) {\n        // Simulate \"recurse\" with \"push\"\n        stack.push(i)\n    }\n    // Return: return result\n    while (stack.isNotEmpty()) {\n        // Simulate \"return\" with \"pop\"\n        res += stack.pop()\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
recursion.rb
### Use iteration to simulate recursion ###\ndef for_loop_recur(n)\n  # Use an explicit stack to simulate the system call stack\n  stack = []\n  res = 0\n\n  # Recurse: recursive call\n  for i in n.downto(0)\n    # Simulate \"recurse\" with \"push\"\n    stack << i\n  end\n  # Return: return result\n  while !stack.empty?\n    res += stack.pop\n  end\n\n  # res = 1+2+3+...+n\n  res\nend\n

Observing the above code, when recursion is transformed into iteration, the code becomes more complex. Although iteration and recursion can be converted into each other in many cases, it may not be worthwhile to do so for the following two reasons.

  • The transformed code may be more difficult to understand and less readable.
  • For some complex problems, simulating the behavior of the system call stack can be very difficult.

In summary, choosing between iteration and recursion depends on the nature of the specific problem. In programming practice, it is crucial to weigh the pros and cons of both and choose the appropriate method based on the context.

","path":["Chapter 2. Complexity Analysis","2.2   Iteration and Recursion"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/","level":1,"title":"2.1   Algorithm Efficiency Evaluation","text":"

In algorithm design, we pursue the following two levels of objectives sequentially.

  1. Finding a solution to the problem: The algorithm must reliably obtain the correct solution within the specified input range.
  2. Seeking the optimal solution: Multiple solutions may exist for the same problem, and we hope to find an algorithm that is as efficient as possible.

In other words, under the premise of being able to solve the problem, algorithm efficiency has become the primary evaluation criterion for measuring the quality of algorithms. It includes the following two dimensions.

  • Time efficiency: The length of time the algorithm runs.
  • Space efficiency: The size of memory space the algorithm occupies.

In short, our goal is to design data structures and algorithms that are \"both fast and memory-efficient\". Effectively evaluating algorithm efficiency is crucial, because only in this way can we compare various algorithms and guide the algorithm design and optimization process.

Efficiency evaluation methods are mainly divided into two types: actual testing and theoretical estimation.

","path":["Chapter 2. Complexity Analysis","2.1   Algorithm Efficiency Evaluation"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/#211-actual-testing","level":2,"title":"2.1.1   Actual Testing","text":"

Suppose we now have algorithm A and algorithm B, both of which can solve the same problem, and we need to compare the efficiency of these two algorithms. The most direct method is to find a computer, run these two algorithms, and monitor and record their running time and memory usage. This evaluation approach can reflect the real situation, but it also has considerable limitations.

On one hand, it is difficult to eliminate interference factors from the testing environment. Hardware configuration affects the performance of algorithms. For example, if an algorithm has a high degree of parallelism, it is more suitable for running on multi-core CPUs; if an algorithm has intensive memory operations, it will perform better on high-performance memory. In other words, the test results of an algorithm on different machines may be inconsistent. This means we need to test on various machines and calculate average efficiency, which is impractical.

On the other hand, conducting complete testing is very resource-intensive. As the input data volume changes, the algorithm will exhibit different efficiencies. For example, when the input data volume is small, the running time of algorithm A is shorter than algorithm B; but when the input data volume is large, the test results may be exactly the opposite. Therefore, to obtain convincing conclusions, we need to test input data of various scales, which requires a large amount of computational resources.

","path":["Chapter 2. Complexity Analysis","2.1   Algorithm Efficiency Evaluation"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/#212-theoretical-estimation","level":2,"title":"2.1.2   Theoretical Estimation","text":"

Since actual testing has considerable limitations, we can consider evaluating algorithm efficiency through calculations alone. This estimation method is called asymptotic complexity analysis, or complexity analysis for short.

Complexity analysis can reflect the relationship between the time and space resources required for algorithm execution and the input data scale. It describes the growth trend of the time and space required for algorithm execution as the input data scale increases. This definition is somewhat convoluted, so we can break it down into three key points to understand.

  • \"Time and space resources\" correspond to time complexity and space complexity, respectively.
  • \"As the input data scale increases\" means that complexity reflects the relationship between algorithm running efficiency and input data scale.
  • \"Growth trend of time and space\" indicates that complexity analysis focuses not on the specific values of running time or occupied space, but on how \"fast\" time or space grows.

Complexity analysis overcomes the drawbacks of the actual testing method, reflected in the following aspects.

  • It does not need to actually run the code, making it more environmentally friendly and energy-efficient.
  • It is independent of the testing environment, and the analysis results are applicable to all running platforms.
  • It can reflect algorithm efficiency at different data volumes, especially algorithm performance at large data volumes.

Tip

If you are still confused about the concept of complexity, don't worry—we will introduce it in detail in subsequent chapters.

Complexity analysis provides us with a \"ruler\" for evaluating algorithm efficiency, allowing us to measure the time and space resources required to execute a certain algorithm and compare the efficiency between different algorithms.

Complexity is a mathematical concept that may be relatively abstract for beginners, with a relatively high learning difficulty. From this perspective, complexity analysis may not be very suitable as the first content to be introduced. However, when we discuss the characteristics of a certain data structure or algorithm, it is difficult to avoid analyzing its running speed and space usage.

In summary, it is recommended that before diving deep into data structures and algorithms, you first establish a preliminary understanding of complexity analysis so that you can complete complexity analysis of simple algorithms.

","path":["Chapter 2. Complexity Analysis","2.1   Algorithm Efficiency Evaluation"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/","level":1,"title":"2.4   Space Complexity","text":"

Space complexity measures the growth trend of memory space occupied by an algorithm as the data size increases. This concept is very similar to time complexity, except that \"running time\" is replaced with \"occupied memory space\".

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#241-algorithm-related-space","level":2,"title":"2.4.1   Algorithm-Related Space","text":"

The memory space used by an algorithm during execution mainly includes the following types.

  • Input space: Used to store the input data of the algorithm.
  • Temporary space: Used to store variables, objects, function contexts, and other data during the algorithm's execution.
  • Output space: Used to store the output data of the algorithm.

In general, the scope of space complexity statistics is \"temporary space\" plus \"output space\".

Temporary space can be further divided into three parts.

  • Temporary data: Used to save various constants, variables, objects, etc., during the algorithm's execution.
  • Stack frame space: Used to save the context data of called functions. The system creates a stack frame at the top of the stack each time a function is called, and the stack frame space is released after the function returns.
  • Instruction space: Used to save compiled program instructions, which are usually ignored in actual statistics.

When analyzing the space complexity of a program, we usually count three parts: temporary data, stack frame space, and output data, as shown in the following figure.

Figure 2-15   Algorithm-related space

The related code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
class Node:\n    \"\"\"Class\"\"\"\n    def __init__(self, x: int):\n        self.val: int = x              # Node value\n        self.next: Node | None = None  # Reference to the next node\n\ndef function() -> int:\n    \"\"\"Function\"\"\"\n    # Perform some operations...\n    return 0\n\ndef algorithm(n) -> int:  # Input data\n    A = 0                 # Temporary data (constant, usually represented by uppercase letters)\n    b = 0                 # Temporary data (variable)\n    node = Node(0)        # Temporary data (object)\n    c = function()        # Stack frame space (function call)\n    return A + b + c      # Output data\n
/* Structure */\nstruct Node {\n    int val;\n    Node *next;\n    Node(int x) : val(x), next(nullptr) {}\n};\n\n/* Function */\nint func() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) {        // Input data\n    const int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node* node = new Node(0); // Temporary data (object)\n    int c = func();           // Stack frame space (function call)\n    return a + b + c;         // Output data\n}\n
/* Class */\nclass Node {\n    int val;\n    Node next;\n    Node(int x) { val = x; }\n}\n\n/* Function */\nint function() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) {        // Input data\n    final int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node node = new Node(0);  // Temporary data (object)\n    int c = function();       // Stack frame space (function call)\n    return a + b + c;         // Output data\n}\n
/* Class */\nclass Node(int x) {\n    int val = x;\n    Node next;\n}\n\n/* Function */\nint Function() {\n    // Perform some operations...\n    return 0;\n}\n\nint Algorithm(int n) {        // Input data\n    const int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node node = new(0);       // Temporary data (object)\n    int c = Function();       // Stack frame space (function call)\n    return a + b + c;         // Output data\n}\n
/* Structure */\ntype node struct {\n    val  int\n    next *node\n}\n\n/* Create node structure */\nfunc newNode(val int) *node {\n    return &node{val: val}\n}\n\n/* Function */\nfunc function() int {\n    // Perform some operations...\n    return 0\n}\n\nfunc algorithm(n int) int { // Input data\n    const a = 0             // Temporary data (constant)\n    b := 0                  // Temporary data (variable)\n    newNode(0)              // Temporary data (object)\n    c := function()         // Stack frame space (function call)\n    return a + b + c        // Output data\n}\n
/* Class */\nclass Node {\n    var val: Int\n    var next: Node?\n\n    init(x: Int) {\n        val = x\n    }\n}\n\n/* Function */\nfunc function() -> Int {\n    // Perform some operations...\n    return 0\n}\n\nfunc algorithm(n: Int) -> Int { // Input data\n    let a = 0             // Temporary data (constant)\n    var b = 0             // Temporary data (variable)\n    let node = Node(x: 0) // Temporary data (object)\n    let c = function()    // Stack frame space (function call)\n    return a + b + c      // Output data\n}\n
/* Class */\nclass Node {\n    val;\n    next;\n    constructor(val) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.next = null;                       // Reference to the next node\n    }\n}\n\n/* Function */\nfunction constFunc() {\n    // Perform some operations\n    return 0;\n}\n\nfunction algorithm(n) {       // Input data\n    const a = 0;              // Temporary data (constant)\n    let b = 0;                // Temporary data (variable)\n    const node = new Node(0); // Temporary data (object)\n    const c = constFunc();    // Stack frame space (function call)\n    return a + b + c;         // Output data\n}\n
/* Class */\nclass Node {\n    val: number;\n    next: Node | null;\n    constructor(val?: number) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.next = null;                       // Reference to the next node\n    }\n}\n\n/* Function */\nfunction constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n\nfunction algorithm(n: number): number { // Input data\n    const a = 0;                        // Temporary data (constant)\n    let b = 0;                          // Temporary data (variable)\n    const node = new Node(0);           // Temporary data (object)\n    const c = constFunc();              // Stack frame space (function call)\n    return a + b + c;                   // Output data\n}\n
/* Class */\nclass Node {\n  int val;\n  Node next;\n  Node(this.val, [this.next]);\n}\n\n/* Function */\nint function() {\n  // Perform some operations...\n  return 0;\n}\n\nint algorithm(int n) {  // Input data\n  const int a = 0;      // Temporary data (constant)\n  int b = 0;            // Temporary data (variable)\n  Node node = Node(0);  // Temporary data (object)\n  int c = function();   // Stack frame space (function call)\n  return a + b + c;     // Output data\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Structure */\nstruct Node {\n    val: i32,\n    next: Option<Rc<RefCell<Node>>>,\n}\n\n/* Create Node structure */\nimpl Node {\n    fn new(val: i32) -> Self {\n        Self { val: val, next: None }\n    }\n}\n\n/* Function */\nfn function() -> i32 {\n    // Perform some operations...\n    return 0;\n}\n\nfn algorithm(n: i32) -> i32 {       // Input data\n    const a: i32 = 0;               // Temporary data (constant)\n    let mut b = 0;                  // Temporary data (variable)\n    let node = Node::new(0);        // Temporary data (object)\n    let c = function();             // Stack frame space (function call)\n    return a + b + c;               // Output data\n}\n
/* Function */\nint func() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) { // Input data\n    const int a = 0;   // Temporary data (constant)\n    int b = 0;         // Temporary data (variable)\n    int c = func();    // Stack frame space (function call)\n    return a + b + c;  // Output data\n}\n
/* Class */\nclass Node(var _val: Int) {\n    var next: Node? = null\n}\n\n/* Function */\nfun function(): Int {\n    // Perform some operations...\n    return 0\n}\n\nfun algorithm(n: Int): Int { // Input data\n    val a = 0                // Temporary data (constant)\n    var b = 0                // Temporary data (variable)\n    val node = Node(0)       // Temporary data (object)\n    val c = function()       // Stack frame space (function call)\n    return a + b + c         // Output data\n}\n
### Class ###\nclass Node\n    attr_accessor :val      # Node value\n    attr_accessor :next     # Reference to the next node\n\n    def initialize(x)\n        @val = x\n    end\nend\n\n### Function ###\ndef function\n    # Perform some operations...\n    0\nend\n\n### Algorithm ###\ndef algorithm(n)        # Input data\n    a = 0               # Temporary data (constant)\n    b = 0               # Temporary data (variable)\n    node = Node.new(0)  # Temporary data (object)\n    c = function        # Stack frame space (function call)\n    a + b + c           # Output data\nend\n
","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#242-calculation-method","level":2,"title":"2.4.2   Calculation Method","text":"

The calculation method for space complexity is roughly the same as for time complexity, except that the statistical object is changed from \"number of operations\" to \"size of space used\".

Unlike time complexity, we usually only focus on the worst-case space complexity. This is because memory space is a hard requirement, and we must ensure that sufficient memory space is reserved for all input data.

Observe the following code. The \"worst case\" in worst-case space complexity has two meanings.

  1. Based on the worst input data: When \\(n < 10\\), the space complexity is \\(O(1)\\); but when \\(n > 10\\), the initialized array nums occupies \\(O(n)\\) space, so the worst-case space complexity is \\(O(n)\\).
  2. Based on the peak memory during algorithm execution: For example, before executing the last line, the program occupies \\(O(1)\\) space; when initializing the array nums, the program occupies \\(O(n)\\) space, so the worst-case space complexity is \\(O(n)\\).
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
def algorithm(n: int):\n    a = 0               # O(1)\n    b = [0] * 10000     # O(1)\n    if n > 10:\n        nums = [0] * n  # O(n)\n
void algorithm(int n) {\n    int a = 0;               // O(1)\n    vector<int> b(10000);    // O(1)\n    if (n > 10)\n        vector<int> nums(n); // O(n)\n}\n
void algorithm(int n) {\n    int a = 0;                   // O(1)\n    int[] b = new int[10000];    // O(1)\n    if (n > 10)\n        int[] nums = new int[n]; // O(n)\n}\n
void Algorithm(int n) {\n    int a = 0;                   // O(1)\n    int[] b = new int[10000];    // O(1)\n    if (n > 10) {\n        int[] nums = new int[n]; // O(n)\n    }\n}\n
func algorithm(n int) {\n    a := 0                      // O(1)\n    b := make([]int, 10000)     // O(1)\n    var nums []int\n    if n > 10 {\n        nums := make([]int, n)  // O(n)\n    }\n    fmt.Println(a, b, nums)\n}\n
func algorithm(n: Int) {\n    let a = 0 // O(1)\n    let b = Array(repeating: 0, count: 10000) // O(1)\n    if n > 10 {\n        let nums = Array(repeating: 0, count: n) // O(n)\n    }\n}\n
function algorithm(n) {\n    const a = 0;                   // O(1)\n    const b = new Array(10000);    // O(1)\n    if (n > 10) {\n        const nums = new Array(n); // O(n)\n    }\n}\n
function algorithm(n: number): void {\n    const a = 0;                   // O(1)\n    const b = new Array(10000);    // O(1)\n    if (n > 10) {\n        const nums = new Array(n); // O(n)\n    }\n}\n
void algorithm(int n) {\n  int a = 0;                            // O(1)\n  List<int> b = List.filled(10000, 0);  // O(1)\n  if (n > 10) {\n    List<int> nums = List.filled(n, 0); // O(n)\n  }\n}\n
fn algorithm(n: i32) {\n    let a = 0;                              // O(1)\n    let b = [0; 10000];                     // O(1)\n    if n > 10 {\n        let nums = vec![0; n as usize];     // O(n)\n    }\n}\n
void algorithm(int n) {\n    int a = 0;               // O(1)\n    int b[10000];            // O(1)\n    if (n > 10)\n        int nums[n] = {0};   // O(n)\n}\n
fun algorithm(n: Int) {\n    val a = 0                    // O(1)\n    val b = IntArray(10000)      // O(1)\n    if (n > 10) {\n        val nums = IntArray(n)   // O(n)\n    }\n}\n
def algorithm(n)\n    a = 0                           # O(1)\n    b = Array.new(10000)            # O(1)\n    nums = Array.new(n) if n > 10   # O(n)\nend\n

In recursive functions, it is necessary to count the stack frame space. Observe the following code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
def function() -> int:\n    # Perform some operations\n    return 0\n\ndef loop(n: int):\n    \"\"\"Loop has space complexity of O(1)\"\"\"\n    for _ in range(n):\n        function()\n\ndef recur(n: int):\n    \"\"\"Recursion has space complexity of O(n)\"\"\"\n    if n == 1:\n        return\n    return recur(n - 1)\n
int func() {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n/* Recursion has space complexity of O(n) */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
int function() {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        function();\n    }\n}\n/* Recursion has space complexity of O(n) */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
int Function() {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nvoid Loop(int n) {\n    for (int i = 0; i < n; i++) {\n        Function();\n    }\n}\n/* Recursion has space complexity of O(n) */\nint Recur(int n) {\n    if (n == 1) return 1;\n    return Recur(n - 1);\n}\n
func function() int {\n    // Perform some operations\n    return 0\n}\n\n/* Loop has space complexity of O(1) */\nfunc loop(n int) {\n    for i := 0; i < n; i++ {\n        function()\n    }\n}\n\n/* Recursion has space complexity of O(n) */\nfunc recur(n int) {\n    if n == 1 {\n        return\n    }\n    recur(n - 1)\n}\n
@discardableResult\nfunc function() -> Int {\n    // Perform some operations\n    return 0\n}\n\n/* Loop has space complexity of O(1) */\nfunc loop(n: Int) {\n    for _ in 0 ..< n {\n        function()\n    }\n}\n\n/* Recursion has space complexity of O(n) */\nfunc recur(n: Int) {\n    if n == 1 {\n        return\n    }\n    recur(n: n - 1)\n}\n
function constFunc() {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nfunction loop(n) {\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n/* Recursion has space complexity of O(n) */\nfunction recur(n) {\n    if (n === 1) return;\n    return recur(n - 1);\n}\n
function constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nfunction loop(n: number): void {\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n/* Recursion has space complexity of O(n) */\nfunction recur(n: number): void {\n    if (n === 1) return;\n    return recur(n - 1);\n}\n
int function() {\n  // Perform some operations\n  return 0;\n}\n/* Loop has space complexity of O(1) */\nvoid loop(int n) {\n  for (int i = 0; i < n; i++) {\n    function();\n  }\n}\n/* Recursion has space complexity of O(n) */\nvoid recur(int n) {\n  if (n == 1) return;\n  recur(n - 1);\n}\n
fn function() -> i32 {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nfn loop(n: i32) {\n    for i in 0..n {\n        function();\n    }\n}\n/* Recursion has space complexity of O(n) */\nfn recur(n: i32) {\n    if n == 1 {\n        return;\n    }\n    recur(n - 1);\n}\n
int func() {\n    // Perform some operations\n    return 0;\n}\n/* Loop has space complexity of O(1) */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n/* Recursion has space complexity of O(n) */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
fun function(): Int {\n    // Perform some operations\n    return 0\n}\n/* Loop has space complexity of O(1) */\nfun loop(n: Int) {\n    for (i in 0..<n) {\n        function()\n    }\n}\n/* Recursion has space complexity of O(n) */\nfun recur(n: Int) {\n    if (n == 1) return\n    return recur(n - 1)\n}\n
def function\n    # Perform some operations\n    0\nend\n\n### Loop has space complexity of O(1) ###\ndef loop(n)\n    (0...n).each { function }\nend\n\n### Recursion has space complexity of O(n) ###\ndef recur(n)\n    return if n == 1\n    recur(n - 1)\nend\n

The time complexity of both functions loop() and recur() is \\(O(n)\\), but their space complexities are different.

  • The function loop() calls function() \\(n\\) times in a loop. In each iteration, function() returns and releases its stack frame space, so the space complexity remains \\(O(1)\\).
  • The recursive function recur() has \\(n\\) unreturned recur() instances existing simultaneously during execution, thus occupying \\(O(n)\\) stack frame space.
","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#243-common-types","level":2,"title":"2.4.3   Common Types","text":"

Let the input data size be \\(n\\). The following figure shows common types of space complexity (arranged from low to high).

\\[ \\begin{aligned} O(1) < O(\\log n) < O(n) < O(n^2) < O(2^n) \\newline \\text{Constant} < \\text{Logarithmic} < \\text{Linear} < \\text{Quadratic} < \\text{Exponential} \\end{aligned} \\]

Figure 2-16   Common types of space complexity

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#1-constant-order-o1","level":3,"title":"1.   Constant Order \\(O(1)\\)","text":"

Constant order is common in constants, variables, and objects whose quantity is independent of the input data size \\(n\\).

It should be noted that memory occupied by initializing variables or calling functions in a loop is released when entering the next iteration, so it does not accumulate space, and the space complexity remains \\(O(1)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def function() -> int:\n    \"\"\"Function\"\"\"\n    # Perform some operations\n    return 0\n\ndef constant(n: int):\n    \"\"\"Constant order\"\"\"\n    # Constants, variables, objects occupy O(1) space\n    a = 0\n    nums = [0] * 10000\n    node = ListNode(0)\n    # Variables in the loop occupy O(1) space\n    for _ in range(n):\n        c = 0\n    # Functions in the loop occupy O(1) space\n    for _ in range(n):\n        function()\n
space_complexity.cpp
/* Function */\nint func() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nvoid constant(int n) {\n    // Constants, variables, objects occupy O(1) space\n    const int a = 0;\n    int b = 0;\n    vector<int> nums(10000);\n    ListNode node(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n
space_complexity.java
/* Function */\nint function() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nvoid constant(int n) {\n    // Constants, variables, objects occupy O(1) space\n    final int a = 0;\n    int b = 0;\n    int[] nums = new int[10000];\n    ListNode node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        function();\n    }\n}\n
space_complexity.cs
/* Function */\nint Function() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nvoid Constant(int n) {\n    // Constants, variables, objects occupy O(1) space\n    int a = 0;\n    int b = 0;\n    int[] nums = new int[10000];\n    ListNode node = new(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        Function();\n    }\n}\n
space_complexity.go
/* Function */\nfunc function() int {\n    // Perform some operations...\n    return 0\n}\n\n/* Constant order */\nfunc spaceConstant(n int) {\n    // Constants, variables, objects occupy O(1) space\n    const a = 0\n    b := 0\n    nums := make([]int, 10000)\n    node := newNode(0)\n    // Variables in the loop occupy O(1) space\n    var c int\n    for i := 0; i < n; i++ {\n        c = 0\n    }\n    // Functions in the loop occupy O(1) space\n    for i := 0; i < n; i++ {\n        function()\n    }\n    b += 0\n    c += 0\n    nums[0] = 0\n    node.val = 0\n}\n
space_complexity.swift
/* Function */\n@discardableResult\nfunc function() -> Int {\n    // Perform some operations\n    return 0\n}\n\n/* Constant order */\nfunc constant(n: Int) {\n    // Constants, variables, objects occupy O(1) space\n    let a = 0\n    var b = 0\n    let nums = Array(repeating: 0, count: 10000)\n    let node = ListNode(x: 0)\n    // Variables in the loop occupy O(1) space\n    for _ in 0 ..< n {\n        let c = 0\n    }\n    // Functions in the loop occupy O(1) space\n    for _ in 0 ..< n {\n        function()\n    }\n}\n
space_complexity.js
/* Function */\nfunction constFunc() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nfunction constant(n) {\n    // Constants, variables, objects occupy O(1) space\n    const a = 0;\n    const b = 0;\n    const nums = new Array(10000);\n    const node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        const c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n
space_complexity.ts
/* Function */\nfunction constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nfunction constant(n: number): void {\n    // Constants, variables, objects occupy O(1) space\n    const a = 0;\n    const b = 0;\n    const nums = new Array(10000);\n    const node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        const c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n
space_complexity.dart
/* Function */\nint function() {\n  // Perform some operations\n  return 0;\n}\n\n/* Constant order */\nvoid constant(int n) {\n  // Constants, variables, objects occupy O(1) space\n  final int a = 0;\n  int b = 0;\n  List<int> nums = List.filled(10000, 0);\n  ListNode node = ListNode(0);\n  // Variables in the loop occupy O(1) space\n  for (var i = 0; i < n; i++) {\n    int c = 0;\n  }\n  // Functions in the loop occupy O(1) space\n  for (var i = 0; i < n; i++) {\n    function();\n  }\n}\n
space_complexity.rs
/* Function */\nfn function() -> i32 {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\n#[allow(unused)]\nfn constant(n: i32) {\n    // Constants, variables, objects occupy O(1) space\n    const A: i32 = 0;\n    let b = 0;\n    let nums = vec![0; 10000];\n    let node = ListNode::new(0);\n    // Variables in the loop occupy O(1) space\n    for i in 0..n {\n        let c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for i in 0..n {\n        function();\n    }\n}\n
space_complexity.c
/* Function */\nint func() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant order */\nvoid constant(int n) {\n    // Constants, variables, objects occupy O(1) space\n    const int a = 0;\n    int b = 0;\n    int nums[1000];\n    ListNode *node = newListNode(0);\n    free(node);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Functions in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n
space_complexity.kt
/* Function */\nfun function(): Int {\n    // Perform some operations\n    return 0\n}\n\n/* Constant order */\nfun constant(n: Int) {\n    // Constants, variables, objects occupy O(1) space\n    val a = 0\n    var b = 0\n    val nums = Array(10000) { 0 }\n    val node = ListNode(0)\n    // Variables in the loop occupy O(1) space\n    for (i in 0..<n) {\n        val c = 0\n    }\n    // Functions in the loop occupy O(1) space\n    for (i in 0..<n) {\n        function()\n    }\n}\n
space_complexity.rb
### Function ###\ndef function\n  # Perform some operations\n  0\nend\n\n### Constant time ###\ndef constant(n)\n  # Constants, variables, objects occupy O(1) space\n  a = 0\n  nums = [0] * 10000\n  node = ListNode.new\n\n  # Variables in the loop occupy O(1) space\n  (0...n).each { c = 0 }\n  # Functions in the loop occupy O(1) space\n  (0...n).each { function }\nend\n
","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#2-linear-order-on","level":3,"title":"2.   Linear Order \\(O(n)\\)","text":"

Linear order is common in arrays, linked lists, stacks, queues, etc., where the number of elements is proportional to \\(n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def linear(n: int):\n    \"\"\"Linear order\"\"\"\n    # A list of length n occupies O(n) space\n    nums = [0] * n\n    # A hash table of length n occupies O(n) space\n    hmap = dict[int, str]()\n    for i in range(n):\n        hmap[i] = str(i)\n
space_complexity.cpp
/* Linear order */\nvoid linear(int n) {\n    // Array of length n uses O(n) space\n    vector<int> nums(n);\n    // A list of length n occupies O(n) space\n    vector<ListNode> nodes;\n    for (int i = 0; i < n; i++) {\n        nodes.push_back(ListNode(i));\n    }\n    // A hash table of length n occupies O(n) space\n    unordered_map<int, string> map;\n    for (int i = 0; i < n; i++) {\n        map[i] = to_string(i);\n    }\n}\n
space_complexity.java
/* Linear order */\nvoid linear(int n) {\n    // Array of length n uses O(n) space\n    int[] nums = new int[n];\n    // A list of length n occupies O(n) space\n    List<ListNode> nodes = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        nodes.add(new ListNode(i));\n    }\n    // A hash table of length n occupies O(n) space\n    Map<Integer, String> map = new HashMap<>();\n    for (int i = 0; i < n; i++) {\n        map.put(i, String.valueOf(i));\n    }\n}\n
space_complexity.cs
/* Linear order */\nvoid Linear(int n) {\n    // Array of length n uses O(n) space\n    int[] nums = new int[n];\n    // A list of length n occupies O(n) space\n    List<ListNode> nodes = [];\n    for (int i = 0; i < n; i++) {\n        nodes.Add(new ListNode(i));\n    }\n    // A hash table of length n occupies O(n) space\n    Dictionary<int, string> map = [];\n    for (int i = 0; i < n; i++) {\n        map.Add(i, i.ToString());\n    }\n}\n
space_complexity.go
/* Linear order */\nfunc spaceLinear(n int) {\n    // Array of length n uses O(n) space\n    _ = make([]int, n)\n    // A list of length n occupies O(n) space\n    var nodes []*node\n    for i := 0; i < n; i++ {\n        nodes = append(nodes, newNode(i))\n    }\n    // A hash table of length n occupies O(n) space\n    m := make(map[int]string, n)\n    for i := 0; i < n; i++ {\n        m[i] = strconv.Itoa(i)\n    }\n}\n
space_complexity.swift
/* Linear order */\nfunc linear(n: Int) {\n    // Array of length n uses O(n) space\n    let nums = Array(repeating: 0, count: n)\n    // A list of length n occupies O(n) space\n    let nodes = (0 ..< n).map { ListNode(x: $0) }\n    // A hash table of length n occupies O(n) space\n    let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, \"\\($0)\") })\n}\n
space_complexity.js
/* Linear order */\nfunction linear(n) {\n    // Array of length n uses O(n) space\n    const nums = new Array(n);\n    // A list of length n occupies O(n) space\n    const nodes = [];\n    for (let i = 0; i < n; i++) {\n        nodes.push(new ListNode(i));\n    }\n    // A hash table of length n occupies O(n) space\n    const map = new Map();\n    for (let i = 0; i < n; i++) {\n        map.set(i, i.toString());\n    }\n}\n
space_complexity.ts
/* Linear order */\nfunction linear(n: number): void {\n    // Array of length n uses O(n) space\n    const nums = new Array(n);\n    // A list of length n occupies O(n) space\n    const nodes: ListNode[] = [];\n    for (let i = 0; i < n; i++) {\n        nodes.push(new ListNode(i));\n    }\n    // A hash table of length n occupies O(n) space\n    const map = new Map();\n    for (let i = 0; i < n; i++) {\n        map.set(i, i.toString());\n    }\n}\n
space_complexity.dart
/* Linear order */\nvoid linear(int n) {\n  // Array of length n uses O(n) space\n  List<int> nums = List.filled(n, 0);\n  // A list of length n occupies O(n) space\n  List<ListNode> nodes = [];\n  for (var i = 0; i < n; i++) {\n    nodes.add(ListNode(i));\n  }\n  // A hash table of length n occupies O(n) space\n  Map<int, String> map = HashMap();\n  for (var i = 0; i < n; i++) {\n    map.putIfAbsent(i, () => i.toString());\n  }\n}\n
space_complexity.rs
/* Linear order */\n#[allow(unused)]\nfn linear(n: i32) {\n    // Array of length n uses O(n) space\n    let mut nums = vec![0; n as usize];\n    // A list of length n occupies O(n) space\n    let mut nodes = Vec::new();\n    for i in 0..n {\n        nodes.push(ListNode::new(i))\n    }\n    // A hash table of length n occupies O(n) space\n    let mut map = HashMap::new();\n    for i in 0..n {\n        map.insert(i, i.to_string());\n    }\n}\n
space_complexity.c
/* Hash table */\ntypedef struct {\n    int key;\n    int val;\n    UT_hash_handle hh; // Implemented using uthash.h\n} HashTable;\n\n/* Linear order */\nvoid linear(int n) {\n    // Array of length n uses O(n) space\n    int *nums = malloc(sizeof(int) * n);\n    free(nums);\n\n    // A list of length n occupies O(n) space\n    ListNode **nodes = malloc(sizeof(ListNode *) * n);\n    for (int i = 0; i < n; i++) {\n        nodes[i] = newListNode(i);\n    }\n    // Memory release\n    for (int i = 0; i < n; i++) {\n        free(nodes[i]);\n    }\n    free(nodes);\n\n    // A hash table of length n occupies O(n) space\n    HashTable *h = NULL;\n    for (int i = 0; i < n; i++) {\n        HashTable *tmp = malloc(sizeof(HashTable));\n        tmp->key = i;\n        tmp->val = i;\n        HASH_ADD_INT(h, key, tmp);\n    }\n\n    // Memory release\n    HashTable *curr, *tmp;\n    HASH_ITER(hh, h, curr, tmp) {\n        HASH_DEL(h, curr);\n        free(curr);\n    }\n}\n
space_complexity.kt
/* Linear order */\nfun linear(n: Int) {\n    // Array of length n uses O(n) space\n    val nums = Array(n) { 0 }\n    // A list of length n occupies O(n) space\n    val nodes = mutableListOf<ListNode>()\n    for (i in 0..<n) {\n        nodes.add(ListNode(i))\n    }\n    // A hash table of length n occupies O(n) space\n    val map = mutableMapOf<Int, String>()\n    for (i in 0..<n) {\n        map[i] = i.toString()\n    }\n}\n
space_complexity.rb
### Linear time ###\ndef linear(n)\n  # A list of length n occupies O(n) space\n  nums = Array.new(n, 0)\n\n  # A hash table of length n occupies O(n) space\n  hmap = {}\n  for i in 0...n\n    hmap[i] = i.to_s\n  end\nend\n

As shown in the following figure, the recursion depth of this function is \\(n\\), meaning that there are \\(n\\) unreturned linear_recur() functions existing simultaneously, using \\(O(n)\\) stack frame space:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def linear_recur(n: int):\n    \"\"\"Linear order (recursive implementation)\"\"\"\n    print(\"Recursion n =\", n)\n    if n == 1:\n        return\n    linear_recur(n - 1)\n
space_complexity.cpp
/* Linear order (recursive implementation) */\nvoid linearRecur(int n) {\n    cout << \"Recursion n = \" << n << endl;\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
space_complexity.java
/* Linear order (recursive implementation) */\nvoid linearRecur(int n) {\n    System.out.println(\"Recursion n = \" + n);\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
space_complexity.cs
/* Linear order (recursive implementation) */\nvoid LinearRecur(int n) {\n    Console.WriteLine(\"Recursion n = \" + n);\n    if (n == 1) return;\n    LinearRecur(n - 1);\n}\n
space_complexity.go
/* Linear order (recursive implementation) */\nfunc spaceLinearRecur(n int) {\n    fmt.Println(\"Recursion n =\", n)\n    if n == 1 {\n        return\n    }\n    spaceLinearRecur(n - 1)\n}\n
space_complexity.swift
/* Linear order (recursive implementation) */\nfunc linearRecur(n: Int) {\n    print(\"Recursion n = \\(n)\")\n    if n == 1 {\n        return\n    }\n    linearRecur(n: n - 1)\n}\n
space_complexity.js
/* Linear order (recursive implementation) */\nfunction linearRecur(n) {\n    console.log(`Recursion n = ${n}`);\n    if (n === 1) return;\n    linearRecur(n - 1);\n}\n
space_complexity.ts
/* Linear order (recursive implementation) */\nfunction linearRecur(n: number): void {\n    console.log(`Recursion n = ${n}`);\n    if (n === 1) return;\n    linearRecur(n - 1);\n}\n
space_complexity.dart
/* Linear order (recursive implementation) */\nvoid linearRecur(int n) {\n  print('Recursion n = $n');\n  if (n == 1) return;\n  linearRecur(n - 1);\n}\n
space_complexity.rs
/* Linear order (recursive implementation) */\nfn linear_recur(n: i32) {\n    println!(\"Recursion n = {}\", n);\n    if n == 1 {\n        return;\n    };\n    linear_recur(n - 1);\n}\n
space_complexity.c
/* Linear order (recursive implementation) */\nvoid linearRecur(int n) {\n    printf(\"Recursion n = %d\\r\\n\", n);\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
space_complexity.kt
/* Linear order (recursive implementation) */\nfun linearRecur(n: Int) {\n    println(\"Recursion n = $n\")\n    if (n == 1)\n        return\n    linearRecur(n - 1)\n}\n
space_complexity.rb
### Linear space (recursive) ###\ndef linear_recur(n)\n  puts \"Recursion n = #{n}\"\n  return if n == 1\n  linear_recur(n - 1)\nend\n

Figure 2-17   Linear order space complexity generated by recursive function

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#3-quadratic-order-on2","level":3,"title":"3.   Quadratic Order \\(O(n^2)\\)","text":"

Quadratic order is common in matrices and graphs, where the number of elements is quadratically related to \\(n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def quadratic(n: int):\n    \"\"\"Quadratic order\"\"\"\n    # A 2D list occupies O(n^2) space\n    num_matrix = [[0] * n for _ in range(n)]\n
space_complexity.cpp
/* Exponential order */\nvoid quadratic(int n) {\n    // 2D list uses O(n^2) space\n    vector<vector<int>> numMatrix;\n    for (int i = 0; i < n; i++) {\n        vector<int> tmp;\n        for (int j = 0; j < n; j++) {\n            tmp.push_back(0);\n        }\n        numMatrix.push_back(tmp);\n    }\n}\n
space_complexity.java
/* Exponential order */\nvoid quadratic(int n) {\n    // Matrix uses O(n^2) space\n    int[][] numMatrix = new int[n][n];\n    // 2D list uses O(n^2) space\n    List<List<Integer>> numList = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        List<Integer> tmp = new ArrayList<>();\n        for (int j = 0; j < n; j++) {\n            tmp.add(0);\n        }\n        numList.add(tmp);\n    }\n}\n
space_complexity.cs
/* Exponential order */\nvoid Quadratic(int n) {\n    // Matrix uses O(n^2) space\n    int[,] numMatrix = new int[n, n];\n    // 2D list uses O(n^2) space\n    List<List<int>> numList = [];\n    for (int i = 0; i < n; i++) {\n        List<int> tmp = [];\n        for (int j = 0; j < n; j++) {\n            tmp.Add(0);\n        }\n        numList.Add(tmp);\n    }\n}\n
space_complexity.go
/* Exponential order */\nfunc spaceQuadratic(n int) {\n    // Matrix uses O(n^2) space\n    numMatrix := make([][]int, n)\n    for i := 0; i < n; i++ {\n        numMatrix[i] = make([]int, n)\n    }\n}\n
space_complexity.swift
/* Exponential order */\nfunc quadratic(n: Int) {\n    // 2D list uses O(n^2) space\n    let numList = Array(repeating: Array(repeating: 0, count: n), count: n)\n}\n
space_complexity.js
/* Exponential order */\nfunction quadratic(n) {\n    // Matrix uses O(n^2) space\n    const numMatrix = Array(n)\n        .fill(null)\n        .map(() => Array(n).fill(null));\n    // 2D list uses O(n^2) space\n    const numList = [];\n    for (let i = 0; i < n; i++) {\n        const tmp = [];\n        for (let j = 0; j < n; j++) {\n            tmp.push(0);\n        }\n        numList.push(tmp);\n    }\n}\n
space_complexity.ts
/* Exponential order */\nfunction quadratic(n: number): void {\n    // Matrix uses O(n^2) space\n    const numMatrix = Array(n)\n        .fill(null)\n        .map(() => Array(n).fill(null));\n    // 2D list uses O(n^2) space\n    const numList = [];\n    for (let i = 0; i < n; i++) {\n        const tmp = [];\n        for (let j = 0; j < n; j++) {\n            tmp.push(0);\n        }\n        numList.push(tmp);\n    }\n}\n
space_complexity.dart
/* Exponential order */\nvoid quadratic(int n) {\n  // Matrix uses O(n^2) space\n  List<List<int>> numMatrix = List.generate(n, (_) => List.filled(n, 0));\n  // 2D list uses O(n^2) space\n  List<List<int>> numList = [];\n  for (var i = 0; i < n; i++) {\n    List<int> tmp = [];\n    for (int j = 0; j < n; j++) {\n      tmp.add(0);\n    }\n    numList.add(tmp);\n  }\n}\n
space_complexity.rs
/* Exponential order */\n#[allow(unused)]\nfn quadratic(n: i32) {\n    // Matrix uses O(n^2) space\n    let num_matrix = vec![vec![0; n as usize]; n as usize];\n    // 2D list uses O(n^2) space\n    let mut num_list = Vec::new();\n    for i in 0..n {\n        let mut tmp = Vec::new();\n        for j in 0..n {\n            tmp.push(0);\n        }\n        num_list.push(tmp);\n    }\n}\n
space_complexity.c
/* Exponential order */\nvoid quadratic(int n) {\n    // 2D list uses O(n^2) space\n    int **numMatrix = malloc(sizeof(int *) * n);\n    for (int i = 0; i < n; i++) {\n        int *tmp = malloc(sizeof(int) * n);\n        for (int j = 0; j < n; j++) {\n            tmp[j] = 0;\n        }\n        numMatrix[i] = tmp;\n    }\n\n    // Memory release\n    for (int i = 0; i < n; i++) {\n        free(numMatrix[i]);\n    }\n    free(numMatrix);\n}\n
space_complexity.kt
/* Exponential order */\nfun quadratic(n: Int) {\n    // Matrix uses O(n^2) space\n    val numMatrix = arrayOfNulls<Array<Int>?>(n)\n    // 2D list uses O(n^2) space\n    val numList = mutableListOf<MutableList<Int>>()\n    for (i in 0..<n) {\n        val tmp = mutableListOf<Int>()\n        for (j in 0..<n) {\n            tmp.add(0)\n        }\n        numList.add(tmp)\n    }\n}\n
space_complexity.rb
### Quadratic time ###\ndef quadratic(n)\n  # 2D list uses O(n^2) space\n  Array.new(n) { Array.new(n, 0) }\nend\n

As shown in the following figure, the recursion depth of this function is \\(n\\), and an array is initialized in each recursive function with lengths of \\(n\\), \\(n-1\\), \\(\\dots\\), \\(2\\), \\(1\\), with an average length of \\(n / 2\\), thus occupying \\(O(n^2)\\) space overall:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def quadratic_recur(n: int) -> int:\n    \"\"\"Quadratic order (recursive implementation)\"\"\"\n    if n <= 0:\n        return 0\n    # Array nums length is n, n-1, ..., 2, 1\n    nums = [0] * n\n    return quadratic_recur(n - 1)\n
space_complexity.cpp
/* Quadratic order (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    vector<int> nums(n);\n    cout << \"In recursion n = \" << n << \", nums length = \" << nums.size() << endl;\n    return quadraticRecur(n - 1);\n}\n
space_complexity.java
/* Quadratic order (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    // Array nums has length n, n-1, ..., 2, 1\n    int[] nums = new int[n];\n    System.out.println(\"In recursion n = \" + n + \", nums length = \" + nums.length);\n    return quadraticRecur(n - 1);\n}\n
space_complexity.cs
/* Quadratic order (recursive implementation) */\nint QuadraticRecur(int n) {\n    if (n <= 0) return 0;\n    int[] nums = new int[n];\n    Console.WriteLine(\"Recursion n = \" + n + \", nums length = \" + nums.Length);\n    return QuadraticRecur(n - 1);\n}\n
space_complexity.go
/* Quadratic order (recursive implementation) */\nfunc spaceQuadraticRecur(n int) int {\n    if n <= 0 {\n        return 0\n    }\n    nums := make([]int, n)\n    fmt.Printf(\"In recursion n = %d, nums length = %d \\n\", n, len(nums))\n    return spaceQuadraticRecur(n - 1)\n}\n
space_complexity.swift
/* Quadratic order (recursive implementation) */\n@discardableResult\nfunc quadraticRecur(n: Int) -> Int {\n    if n <= 0 {\n        return 0\n    }\n    // Array nums has length n, n-1, ..., 2, 1\n    let nums = Array(repeating: 0, count: n)\n    print(\"In recursion n = \\(n), nums length = \\(nums.count)\")\n    return quadraticRecur(n: n - 1)\n}\n
space_complexity.js
/* Quadratic order (recursive implementation) */\nfunction quadraticRecur(n) {\n    if (n <= 0) return 0;\n    const nums = new Array(n);\n    console.log(`In recursion n = ${n}, nums length = ${nums.length}`);\n    return quadraticRecur(n - 1);\n}\n
space_complexity.ts
/* Quadratic order (recursive implementation) */\nfunction quadraticRecur(n: number): number {\n    if (n <= 0) return 0;\n    const nums = new Array(n);\n    console.log(`In recursion n = ${n}, nums length = ${nums.length}`);\n    return quadraticRecur(n - 1);\n}\n
space_complexity.dart
/* Quadratic order (recursive implementation) */\nint quadraticRecur(int n) {\n  if (n <= 0) return 0;\n  List<int> nums = List.filled(n, 0);\n  print('In recursion n = $n, nums length = ${nums.length}');\n  return quadraticRecur(n - 1);\n}\n
space_complexity.rs
/* Quadratic order (recursive implementation) */\nfn quadratic_recur(n: i32) -> i32 {\n    if n <= 0 {\n        return 0;\n    };\n    // Array nums has length n, n-1, ..., 2, 1\n    let nums = vec![0; n as usize];\n    println!(\"In recursion n = {}, nums length = {}\", n, nums.len());\n    return quadratic_recur(n - 1);\n}\n
space_complexity.c
/* Quadratic order (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    int *nums = malloc(sizeof(int) * n);\n    printf(\"In recursion n = %d, nums length = %d\\r\\n\", n, n);\n    int res = quadraticRecur(n - 1);\n    free(nums);\n    return res;\n}\n
space_complexity.kt
/* Quadratic order (recursive implementation) */\ntailrec fun quadraticRecur(n: Int): Int {\n    if (n <= 0)\n        return 0\n    // Array nums has length n, n-1, ..., 2, 1\n    val nums = Array(n) { 0 }\n    println(\"In recursion n = $n, nums length = ${nums.size}\")\n    return quadraticRecur(n - 1)\n}\n
space_complexity.rb
### Quadratic space (recursive) ###\ndef quadratic_recur(n)\n  return 0 unless n > 0\n\n  # Array nums has length n, n-1, ..., 2, 1\n  nums = Array.new(n, 0)\n  quadratic_recur(n - 1)\nend\n

Figure 2-18   Quadratic order space complexity generated by recursive function

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#4-exponential-order-o2n","level":3,"title":"4.   Exponential Order \\(O(2^n)\\)","text":"

Exponential order is common in binary trees. Observe the following figure: a \"full binary tree\" with \\(n\\) levels has \\(2^n - 1\\) nodes, occupying \\(O(2^n)\\) space:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
def build_tree(n: int) -> TreeNode | None:\n    \"\"\"Exponential order (build full binary tree)\"\"\"\n    if n == 0:\n        return None\n    root = TreeNode(0)\n    root.left = build_tree(n - 1)\n    root.right = build_tree(n - 1)\n    return root\n
space_complexity.cpp
/* Driver Code */\nTreeNode *buildTree(int n) {\n    if (n == 0)\n        return nullptr;\n    TreeNode *root = new TreeNode(0);\n    root->left = buildTree(n - 1);\n    root->right = buildTree(n - 1);\n    return root;\n}\n
space_complexity.java
/* Driver Code */\nTreeNode buildTree(int n) {\n    if (n == 0)\n        return null;\n    TreeNode root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
space_complexity.cs
/* Driver Code */\nTreeNode? BuildTree(int n) {\n    if (n == 0) return null;\n    TreeNode root = new(0) {\n        left = BuildTree(n - 1),\n        right = BuildTree(n - 1)\n    };\n    return root;\n}\n
space_complexity.go
/* Driver Code */\nfunc buildTree(n int) *TreeNode {\n    if n == 0 {\n        return nil\n    }\n    root := NewTreeNode(0)\n    root.Left = buildTree(n - 1)\n    root.Right = buildTree(n - 1)\n    return root\n}\n
space_complexity.swift
/* Driver Code */\nfunc buildTree(n: Int) -> TreeNode? {\n    if n == 0 {\n        return nil\n    }\n    let root = TreeNode(x: 0)\n    root.left = buildTree(n: n - 1)\n    root.right = buildTree(n: n - 1)\n    return root\n}\n
space_complexity.js
/* Driver Code */\nfunction buildTree(n) {\n    if (n === 0) return null;\n    const root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
space_complexity.ts
/* Driver Code */\nfunction buildTree(n: number): TreeNode | null {\n    if (n === 0) return null;\n    const root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
space_complexity.dart
/* Driver Code */\nTreeNode? buildTree(int n) {\n  if (n == 0) return null;\n  TreeNode root = TreeNode(0);\n  root.left = buildTree(n - 1);\n  root.right = buildTree(n - 1);\n  return root;\n}\n
space_complexity.rs
/* Driver Code */\nfn build_tree(n: i32) -> Option<Rc<RefCell<TreeNode>>> {\n    if n == 0 {\n        return None;\n    };\n    let root = TreeNode::new(0);\n    root.borrow_mut().left = build_tree(n - 1);\n    root.borrow_mut().right = build_tree(n - 1);\n    return Some(root);\n}\n
space_complexity.c
/* Driver Code */\nTreeNode *buildTree(int n) {\n    if (n == 0)\n        return NULL;\n    TreeNode *root = newTreeNode(0);\n    root->left = buildTree(n - 1);\n    root->right = buildTree(n - 1);\n    return root;\n}\n
space_complexity.kt
/* Driver Code */\nfun buildTree(n: Int): TreeNode? {\n    if (n == 0)\n        return null\n    val root = TreeNode(0)\n    root.left = buildTree(n - 1)\n    root.right = buildTree(n - 1)\n    return root\n}\n
space_complexity.rb
### Exponential space (build full binary tree) ###\ndef build_tree(n)\n  return if n == 0\n\n  TreeNode.new.tap do |root|\n    root.left = build_tree(n - 1)\n    root.right = build_tree(n - 1)\n  end\nend\n

Figure 2-19   Exponential order space complexity generated by full binary tree

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#5-logarithmic-order-olog-n","level":3,"title":"5.   Logarithmic Order \\(O(\\log n)\\)","text":"

Logarithmic order is common in divide-and-conquer algorithms. For example, merge sort: given an input array of length \\(n\\), each recursion divides the array in half from the midpoint, forming a recursion tree of height \\(\\log n\\), using \\(O(\\log n)\\) stack frame space.

Another example is converting a number to a string. Given a positive integer \\(n\\), it has \\(\\lfloor \\log_{10} n \\rfloor + 1\\) digits, i.e., the corresponding string length is \\(\\lfloor \\log_{10} n \\rfloor + 1\\), so the space complexity is \\(O(\\log_{10} n + 1) = O(\\log n)\\).

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#244-trading-time-for-space","level":2,"title":"2.4.4   Trading Time for Space","text":"

Ideally, we hope that both the time complexity and space complexity of an algorithm can reach optimal. However, in practice, optimizing both time complexity and space complexity simultaneously is usually very difficult.

Reducing time complexity usually comes at the cost of increasing space complexity, and vice versa. The approach of sacrificing memory space to improve algorithm execution speed is called \"trading space for time\"; conversely, it is called \"trading time for space\".

The choice of which approach depends on which aspect we value more. In most cases, time is more precious than space, so \"trading space for time\" is usually the more common strategy. Of course, when the data volume is very large, controlling space complexity is also very important.

","path":["Chapter 2. Complexity Analysis","2.4   Space Complexity"],"tags":[]},{"location":"chapter_computational_complexity/summary/","level":1,"title":"2.5   Summary","text":"","path":["Chapter 2. Complexity Analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"

Algorithm Efficiency Assessment

  • Time efficiency and space efficiency are the two primary evaluation metrics for measuring algorithm performance.
  • We can evaluate algorithm efficiency through actual testing, but it is difficult to eliminate the influence of the testing environment, and it consumes substantial computational resources.
  • Complexity analysis can eliminate the drawbacks of actual testing, with results applicable to all running platforms, and it can reveal algorithm efficiency under different data scales.

Time Complexity

  • Time complexity is used to measure the trend of algorithm runtime as data volume increases. It can effectively evaluate algorithm efficiency, but may fail in certain situations, such as when the input data volume is small or when time complexities are identical, making it impossible to precisely compare algorithm efficiency.
  • Worst-case time complexity is represented using Big \\(O\\) notation, corresponding to the asymptotic upper bound of a function, reflecting the growth level of the number of operations \\(T(n)\\) as \\(n\\) approaches positive infinity.
  • Deriving time complexity involves two steps: first, counting the number of operations, then determining the asymptotic upper bound.
  • Common time complexities arranged from low to high include \\(O(1)\\), \\(O(\\log n)\\), \\(O(n)\\), \\(O(n \\log n)\\), \\(O(n^2)\\), \\(O(2^n)\\), and \\(O(n!)\\).
  • The time complexity of some algorithms is not fixed, but rather depends on the distribution of input data. Time complexity is divided into worst-case, best-case, and average-case time complexity. Best-case time complexity is rarely used because input data generally needs to satisfy strict conditions to achieve the best case.
  • Average time complexity reflects the algorithm's runtime efficiency under random data input, and is closest to the algorithm's performance in practical applications. Calculating average time complexity requires statistical analysis of input data distribution and the combined mathematical expectation.

Space Complexity

  • Space complexity serves a similar purpose to time complexity, used to measure the trend of algorithm memory usage as data volume increases.
  • The memory space related to algorithm execution can be divided into input space, temporary space, and output space. Typically, input space is not included in space complexity calculations. Temporary space can be divided into temporary data, stack frame space, and instruction space, where stack frame space usually affects space complexity only in recursive functions.
  • We typically only focus on worst-case space complexity, which is the space complexity of an algorithm under worst-case input data and worst-case runtime.
  • Common space complexities arranged from low to high include \\(O(1)\\), \\(O(\\log n)\\), \\(O(n)\\), \\(O(n^2)\\), and \\(O(2^n)\\).
","path":["Chapter 2. Complexity Analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Is the space complexity of tail recursion \\(O(1)\\)?

Theoretically, the space complexity of tail recursive functions can be optimized to \\(O(1)\\). However, most programming languages (such as Java, Python, C++, Go, C#, etc.) do not support automatic tail recursion optimization, so the space complexity is generally considered to be \\(O(n)\\).

Q: What is the difference between the terms function and method?

A function can be executed independently, with all parameters passed explicitly. A method is associated with an object, is implicitly passed to the object that invokes it, and can operate on data contained in class instances.

The following examples use several common programming languages for illustration.

  • C is a procedural programming language without object-oriented concepts, so it only has functions. However, we can simulate object-oriented programming by creating structures (struct), and functions associated with structures are equivalent to methods in other programming languages.
  • Java and C# are object-oriented programming languages where code blocks (methods) are typically part of a class. Static methods behave like functions because they are bound to the class and cannot access specific instance variables.
  • C++ and Python support both procedural programming (functions) and object-oriented programming (methods).

Q: Does the diagram for \"common space complexity types\" reflect the absolute size of occupied space?

No, the diagram shows space complexity, which reflects growth trends rather than the absolute size of occupied space.

Assuming \\(n = 8\\), you might find that the values of each curve do not correspond to the functions. This is because each curve contains a constant term used to compress the value range into a visually comfortable range.

In practice, because we generally do not know what the \"constant term\" complexity of each method is, we usually cannot select the optimal solution for \\(n = 8\\) based on complexity alone. But for \\(n = 8^5\\), the choice is straightforward, as the growth trend already dominates.

Q: Are there situations where algorithms are designed to sacrifice time (or space) based on actual use cases?

In practical applications, most situations choose to sacrifice space for time. For example, with database indexes, we typically choose to build B+ trees or hash indexes, occupying substantial memory space in exchange for efficient queries of \\(O(\\log n)\\) or even \\(O(1)\\).

In scenarios where space resources are precious, time may be sacrificed for space. For example, in embedded development, device memory is precious, and engineers may forgo using hash tables and choose to use array sequential search to save memory usage, at the cost of slower searches.

","path":["Chapter 2. Complexity Analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/","level":1,"title":"2.3   Time Complexity","text":"

Runtime can intuitively and accurately reflect the efficiency of an algorithm. If we want to accurately estimate the runtime of a piece of code, how should we proceed?

  1. Determine the running platform, including hardware configuration, programming language, system environment, etc., as these factors all affect code execution efficiency.
  2. Evaluate the runtime required for various computational operations, for example, an addition operation + requires 1 ns, a multiplication operation * requires 10 ns, a print operation print() requires 5 ns, etc.
  3. Count all computational operations in the code, and sum the execution times of all operations to obtain the runtime.

For example, in the following code, the input data size is \\(n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
# On a certain running platform\ndef algorithm(n: int):\n    a = 2      # 1 ns\n    a = a + 1  # 1 ns\n    a = a * 2  # 10 ns\n    # Loop n times\n    for _ in range(n):  # 1 ns\n        print(0)        # 5 ns\n
// On a certain running platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Loop n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        cout << 0 << endl;         // 5 ns\n    }\n}\n
// On a certain running platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Loop n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        System.out.println(0);     // 5 ns\n    }\n}\n
// On a certain running platform\nvoid Algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Loop n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        Console.WriteLine(0);      // 5 ns\n    }\n}\n
// On a certain running platform\nfunc algorithm(n int) {\n    a := 2     // 1 ns\n    a = a + 1  // 1 ns\n    a = a * 2  // 10 ns\n    // Loop n times\n    for i := 0; i < n; i++ {  // 1 ns\n        fmt.Println(a)        // 5 ns\n    }\n}\n
// On a certain running platform\nfunc algorithm(n: Int) {\n    var a = 2 // 1 ns\n    a = a + 1 // 1 ns\n    a = a * 2 // 10 ns\n    // Loop n times\n    for _ in 0 ..< n { // 1 ns\n        print(0) // 5 ns\n    }\n}\n
// On a certain running platform\nfunction algorithm(n) {\n    var a = 2; // 1 ns\n    a = a + 1; // 1 ns\n    a = a * 2; // 10 ns\n    // Loop n times\n    for(let i = 0; i < n; i++) { // 1 ns\n        console.log(0); // 5 ns\n    }\n}\n
// On a certain running platform\nfunction algorithm(n: number): void {\n    var a: number = 2; // 1 ns\n    a = a + 1; // 1 ns\n    a = a * 2; // 10 ns\n    // Loop n times\n    for(let i = 0; i < n; i++) { // 1 ns\n        console.log(0); // 5 ns\n    }\n}\n
// On a certain running platform\nvoid algorithm(int n) {\n  int a = 2; // 1 ns\n  a = a + 1; // 1 ns\n  a = a * 2; // 10 ns\n  // Loop n times\n  for (int i = 0; i < n; i++) { // 1 ns\n    print(0); // 5 ns\n  }\n}\n
// On a certain running platform\nfn algorithm(n: i32) {\n    let mut a = 2;      // 1 ns\n    a = a + 1;          // 1 ns\n    a = a * 2;          // 10 ns\n    // Loop n times\n    for _ in 0..n {     // 1 ns\n        println!(\"{}\", 0);  // 5 ns\n    }\n}\n
// On a certain running platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Loop n times\n    for (int i = 0; i < n; i++) {   // 1 ns\n        printf(\"%d\", 0);            // 5 ns\n    }\n}\n
// On a certain running platform\nfun algorithm(n: Int) {\n    var a = 2 // 1 ns\n    a = a + 1 // 1 ns\n    a = a * 2 // 10 ns\n    // Loop n times\n    for (i in 0..<n) {  // 1 ns\n        println(0)      // 5 ns\n    }\n}\n
# On a certain running platform\ndef algorithm(n)\n    a = 2       # 1 ns\n    a = a + 1   # 1 ns\n    a = a * 2   # 10 ns\n    # Loop n times\n    (0...n).each do # 1 ns\n        puts 0      # 5 ns\n    end\nend\n

According to the above method, the algorithm's runtime can be obtained as \\((6n + 12)\\) ns:

\\[ 1 + 1 + 10 + (1 + 5) \\times n = 6n + 12 \\]

In reality, however, counting an algorithm's runtime is neither reasonable nor realistic. First, we do not want to tie the estimated time to the running platform, because algorithms need to run on various different platforms. Second, it is difficult to know the runtime of each type of operation, which brings great difficulty to the estimation process.

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#231-counting-time-growth-trends","level":2,"title":"2.3.1   Counting Time Growth Trends","text":"

Time complexity analysis does not count the algorithm's runtime, but rather counts the growth trend of the algorithm's runtime as the data volume increases.

The concept of \"time growth trend\" is rather abstract; let us understand it through an example. Suppose the input data size is \\(n\\), and given three algorithms A, B, and C:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
# Time complexity of algorithm A: constant order\ndef algorithm_A(n: int):\n    print(0)\n# Time complexity of algorithm B: linear order\ndef algorithm_B(n: int):\n    for _ in range(n):\n        print(0)\n# Time complexity of algorithm C: constant order\ndef algorithm_C(n: int):\n    for _ in range(1000000):\n        print(0)\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n    cout << 0 << endl;\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        cout << 0 << endl;\n    }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        cout << 0 << endl;\n    }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n    System.out.println(0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        System.out.println(0);\n    }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        System.out.println(0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nvoid AlgorithmA(int n) {\n    Console.WriteLine(0);\n}\n// Time complexity of algorithm B: linear order\nvoid AlgorithmB(int n) {\n    for (int i = 0; i < n; i++) {\n        Console.WriteLine(0);\n    }\n}\n// Time complexity of algorithm C: constant order\nvoid AlgorithmC(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        Console.WriteLine(0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nfunc algorithm_A(n int) {\n    fmt.Println(0)\n}\n// Time complexity of algorithm B: linear order\nfunc algorithm_B(n int) {\n    for i := 0; i < n; i++ {\n        fmt.Println(0)\n    }\n}\n// Time complexity of algorithm C: constant order\nfunc algorithm_C(n int) {\n    for i := 0; i < 1000000; i++ {\n        fmt.Println(0)\n    }\n}\n
// Time complexity of algorithm A: constant order\nfunc algorithmA(n: Int) {\n    print(0)\n}\n\n// Time complexity of algorithm B: linear order\nfunc algorithmB(n: Int) {\n    for _ in 0 ..< n {\n        print(0)\n    }\n}\n\n// Time complexity of algorithm C: constant order\nfunc algorithmC(n: Int) {\n    for _ in 0 ..< 1_000_000 {\n        print(0)\n    }\n}\n
// Time complexity of algorithm A: constant order\nfunction algorithm_A(n) {\n    console.log(0);\n}\n// Time complexity of algorithm B: linear order\nfunction algorithm_B(n) {\n    for (let i = 0; i < n; i++) {\n        console.log(0);\n    }\n}\n// Time complexity of algorithm C: constant order\nfunction algorithm_C(n) {\n    for (let i = 0; i < 1000000; i++) {\n        console.log(0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nfunction algorithm_A(n: number): void {\n    console.log(0);\n}\n// Time complexity of algorithm B: linear order\nfunction algorithm_B(n: number): void {\n    for (let i = 0; i < n; i++) {\n        console.log(0);\n    }\n}\n// Time complexity of algorithm C: constant order\nfunction algorithm_C(n: number): void {\n    for (let i = 0; i < 1000000; i++) {\n        console.log(0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithmA(int n) {\n  print(0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithmB(int n) {\n  for (int i = 0; i < n; i++) {\n    print(0);\n  }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithmC(int n) {\n  for (int i = 0; i < 1000000; i++) {\n    print(0);\n  }\n}\n
// Time complexity of algorithm A: constant order\nfn algorithm_A(n: i32) {\n    println!(\"{}\", 0);\n}\n// Time complexity of algorithm B: linear order\nfn algorithm_B(n: i32) {\n    for _ in 0..n {\n        println!(\"{}\", 0);\n    }\n}\n// Time complexity of algorithm C: constant order\nfn algorithm_C(n: i32) {\n    for _ in 0..1000000 {\n        println!(\"{}\", 0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nvoid algorithm_A(int n) {\n    printf(\"%d\", 0);\n}\n// Time complexity of algorithm B: linear order\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        printf(\"%d\", 0);\n    }\n}\n// Time complexity of algorithm C: constant order\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        printf(\"%d\", 0);\n    }\n}\n
// Time complexity of algorithm A: constant order\nfun algoritm_A(n: Int) {\n    println(0)\n}\n// Time complexity of algorithm B: linear order\nfun algorithm_B(n: Int) {\n    for (i in 0..<n){\n        println(0)\n    }\n}\n// Time complexity of algorithm C: constant order\nfun algorithm_C(n: Int) {\n    for (i in 0..<1000000) {\n        println(0)\n    }\n}\n
# Time complexity of algorithm A: constant order\ndef algorithm_A(n)\n    puts 0\nend\n\n# Time complexity of algorithm B: linear order\ndef algorithm_B(n)\n    (0...n).each { puts 0 }\nend\n\n# Time complexity of algorithm C: constant order\ndef algorithm_C(n)\n    (0...1_000_000).each { puts 0 }\nend\n

Figure 2-7 shows the time complexity of the above three algorithm functions.

  • Algorithm A has only \\(1\\) print operation, and the algorithm's runtime does not grow as \\(n\\) increases. We call the time complexity of this algorithm \"constant order\".
  • In algorithm B, the print operation needs to loop \\(n\\) times, and the algorithm's runtime grows linearly as \\(n\\) increases. The time complexity of this algorithm is called \"linear order\".
  • In algorithm C, the print operation needs to loop \\(1000000\\) times. Although the runtime is very long, it is independent of the input data size \\(n\\). Therefore, the time complexity of C is the same as A, still \"constant order\".

Figure 2-7   Time growth trends of algorithms A, B, and C

Compared to directly counting the algorithm's runtime, what are the characteristics of time complexity analysis?

  • Time complexity can effectively evaluate algorithm efficiency. For example, the runtime of algorithm B grows linearly; when \\(n > 1\\) it is slower than algorithm A, and when \\(n > 1000000\\) it is slower than algorithm C. In fact, as long as the input data size \\(n\\) is sufficiently large, an algorithm with \"constant order\" complexity will always be superior to one with \"linear order\" complexity, which is precisely the meaning of time growth trend.
  • The derivation method for time complexity is simpler. Obviously, the running platform and the types of computational operations are both unrelated to the growth trend of the algorithm's runtime. Therefore, in time complexity analysis, we can simply treat the execution time of all computational operations as the same \"unit time\", thus simplifying \"counting computational operation runtime\" to \"counting the number of computational operations\", which greatly reduces the difficulty of estimation.
  • Time complexity also has certain limitations. For example, although algorithms A and C have the same time complexity, their actual runtimes differ significantly. Similarly, although algorithm B has a higher time complexity than C, when the input data size \\(n\\) is small, algorithm B is clearly superior to algorithm C. In such cases, it is often difficult to judge the efficiency of algorithms based solely on time complexity. Of course, despite the above issues, complexity analysis remains the most effective and commonly used method for evaluating algorithm efficiency.
","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#232-asymptotic-upper-bound-of-functions","level":2,"title":"2.3.2   Asymptotic Upper Bound of Functions","text":"

Given a function with input size \\(n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
def algorithm(n: int):\n    a = 1      # +1\n    a = a + 1  # +1\n    a = a * 2  # +1\n    # Loop n times\n    for i in range(n):  # +1\n        print(0)        # +1\n
void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Loop n times\n    for (int i = 0; i < n; i++) { // +1 (i++ is executed each round)\n        cout << 0 << endl;    // +1\n    }\n}\n
void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Loop n times\n    for (int i = 0; i < n; i++) { // +1 (i++ is executed each round)\n        System.out.println(0);    // +1\n    }\n}\n
void Algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Loop n times\n    for (int i = 0; i < n; i++) {   // +1 (i++ is executed each round)\n        Console.WriteLine(0);   // +1\n    }\n}\n
func algorithm(n int) {\n    a := 1      // +1\n    a = a + 1   // +1\n    a = a * 2   // +1\n    // Loop n times\n    for i := 0; i < n; i++ {   // +1\n        fmt.Println(a)         // +1\n    }\n}\n
func algorithm(n: Int) {\n    var a = 1 // +1\n    a = a + 1 // +1\n    a = a * 2 // +1\n    // Loop n times\n    for _ in 0 ..< n { // +1\n        print(0) // +1\n    }\n}\n
function algorithm(n) {\n    var a = 1; // +1\n    a += 1; // +1\n    a *= 2; // +1\n    // Loop n times\n    for(let i = 0; i < n; i++){ // +1 (i++ is executed each round)\n        console.log(0); // +1\n    }\n}\n
function algorithm(n: number): void{\n    var a: number = 1; // +1\n    a += 1; // +1\n    a *= 2; // +1\n    // Loop n times\n    for(let i = 0; i < n; i++){ // +1 (i++ is executed each round)\n        console.log(0); // +1\n    }\n}\n
void algorithm(int n) {\n  int a = 1; // +1\n  a = a + 1; // +1\n  a = a * 2; // +1\n  // Loop n times\n  for (int i = 0; i < n; i++) { // +1 (i++ is executed each round)\n    print(0); // +1\n  }\n}\n
fn algorithm(n: i32) {\n    let mut a = 1;   // +1\n    a = a + 1;      // +1\n    a = a * 2;      // +1\n\n    // Loop n times\n    for _ in 0..n { // +1 (i++ is executed each round)\n        println!(\"{}\", 0); // +1\n    }\n}\n
void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Loop n times\n    for (int i = 0; i < n; i++) {   // +1 (i++ is executed each round)\n        printf(\"%d\", 0);            // +1\n    }\n}\n
fun algorithm(n: Int) {\n    var a = 1 // +1\n    a = a + 1 // +1\n    a = a * 2 // +1\n    // Loop n times\n    for (i in 0..<n) { // +1 (i++ is executed each round)\n        println(0) // +1\n    }\n}\n
def algorithm(n)\n    a = 1       # +1\n    a = a + 1   # +1\n    a = a * 2   # +1\n    # Loop n times\n    (0...n).each do # +1\n        puts 0      # +1\n    end\nend\n

Let the number of operations of the algorithm be a function of the input data size \\(n\\), denoted as \\(T(n)\\). Then the number of operations of the above function is:

\\[ T(n) = 3 + 2n \\]

\\(T(n)\\) is a linear function, indicating that its runtime growth trend is linear, and therefore its time complexity is linear order.

We denote the time complexity of linear order as \\(O(n)\\). This mathematical symbol is called big-\\(O\\) notation, representing the asymptotic upper bound of the function \\(T(n)\\).

Time complexity analysis essentially calculates the asymptotic upper bound of \"the number of operations \\(T(n)\\)\", which has a clear mathematical definition.

Asymptotic upper bound of functions

If there exist positive real numbers \\(c\\) and \\(n_0\\) such that for all \\(n > n_0\\), we have \\(T(n) \\leq c \\cdot f(n)\\), then \\(f(n)\\) can be considered as an asymptotic upper bound of \\(T(n)\\), denoted as \\(T(n) = O(f(n))\\).

As shown in Figure 2-8, calculating the asymptotic upper bound is to find a function \\(f(n)\\) such that when \\(n\\) tends to infinity, \\(T(n)\\) and \\(f(n)\\) are at the same growth level, differing only by a constant coefficient \\(c\\).

Figure 2-8   Asymptotic upper bound of a function

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#233-derivation-method","level":2,"title":"2.3.3   Derivation Method","text":"

The asymptotic upper bound has a bit of mathematical flavor. If you feel you haven't fully understood it, don't worry. We can first master the derivation method, and gradually grasp its mathematical meaning through continuous practice.

According to the definition, after determining \\(f(n)\\), we can obtain the time complexity \\(O(f(n))\\). So how do we determine the asymptotic upper bound \\(f(n)\\)? Overall, it is divided into two steps: first count the number of operations, then determine the asymptotic upper bound.

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#1-step-1-count-the-number-of-operations","level":3,"title":"1.   Step 1: Count the Number of Operations","text":"

For code, count from top to bottom line by line. However, since the constant coefficient \\(c\\) in \\(c \\cdot f(n)\\) above can be of any size, coefficients and constant terms in the number of operations \\(T(n)\\) can all be ignored. According to this principle, the following counting simplification techniques can be summarized.

  1. Ignore constants in \\(T(n)\\). Because they are all independent of \\(n\\), they do not affect time complexity.
  2. Omit all coefficients. For example, looping \\(2n\\) times, \\(5n + 1\\) times, etc., can all be simplified as \\(n\\) times, because the coefficient before \\(n\\) does not affect time complexity.
  3. Use multiplication for nested loops. The total number of operations equals the product of the number of operations in the outer and inner loops, with each layer of loop still able to apply techniques 1. and 2. separately.

Given a function, we can use the above techniques to count the number of operations:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
def algorithm(n: int):\n    a = 1      # +0 (Technique 1)\n    a = a + n  # +0 (Technique 1)\n    # +n (Technique 2)\n    for i in range(5 * n + 1):\n        print(0)\n    # +n*n (Technique 3)\n    for i in range(2 * n):\n        for j in range(n + 1):\n            print(0)\n
void algorithm(int n) {\n    int a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        cout << 0 << endl;\n    }\n    // +n*n (Technique 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            cout << 0 << endl;\n        }\n    }\n}\n
void algorithm(int n) {\n    int a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        System.out.println(0);\n    }\n    // +n*n (Technique 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            System.out.println(0);\n        }\n    }\n}\n
void Algorithm(int n) {\n    int a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        Console.WriteLine(0);\n    }\n    // +n*n (Technique 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            Console.WriteLine(0);\n        }\n    }\n}\n
func algorithm(n int) {\n    a := 1     // +0 (Technique 1)\n    a = a + n  // +0 (Technique 1)\n    // +n (Technique 2)\n    for i := 0; i < 5 * n + 1; i++ {\n        fmt.Println(0)\n    }\n    // +n*n (Technique 3)\n    for i := 0; i < 2 * n; i++ {\n        for j := 0; j < n + 1; j++ {\n            fmt.Println(0)\n        }\n    }\n}\n
func algorithm(n: Int) {\n    var a = 1 // +0 (Technique 1)\n    a = a + n // +0 (Technique 1)\n    // +n (Technique 2)\n    for _ in 0 ..< (5 * n + 1) {\n        print(0)\n    }\n    // +n*n (Technique 3)\n    for _ in 0 ..< (2 * n) {\n        for _ in 0 ..< (n + 1) {\n            print(0)\n        }\n    }\n}\n
function algorithm(n) {\n    let a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (let i = 0; i < 5 * n + 1; i++) {\n        console.log(0);\n    }\n    // +n*n (Technique 3)\n    for (let i = 0; i < 2 * n; i++) {\n        for (let j = 0; j < n + 1; j++) {\n            console.log(0);\n        }\n    }\n}\n
function algorithm(n: number): void {\n    let a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (let i = 0; i < 5 * n + 1; i++) {\n        console.log(0);\n    }\n    // +n*n (Technique 3)\n    for (let i = 0; i < 2 * n; i++) {\n        for (let j = 0; j < n + 1; j++) {\n            console.log(0);\n        }\n    }\n}\n
void algorithm(int n) {\n  int a = 1; // +0 (Technique 1)\n  a = a + n; // +0 (Technique 1)\n  // +n (Technique 2)\n  for (int i = 0; i < 5 * n + 1; i++) {\n    print(0);\n  }\n  // +n*n (Technique 3)\n  for (int i = 0; i < 2 * n; i++) {\n    for (int j = 0; j < n + 1; j++) {\n      print(0);\n    }\n  }\n}\n
fn algorithm(n: i32) {\n    let mut a = 1;     // +0 (Technique 1)\n    a = a + n;        // +0 (Technique 1)\n\n    // +n (Technique 2)\n    for i in 0..(5 * n + 1) {\n        println!(\"{}\", 0);\n    }\n\n    // +n*n (Technique 3)\n    for i in 0..(2 * n) {\n        for j in 0..(n + 1) {\n            println!(\"{}\", 0);\n        }\n    }\n}\n
void algorithm(int n) {\n    int a = 1;  // +0 (Technique 1)\n    a = a + n;  // +0 (Technique 1)\n    // +n (Technique 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        printf(\"%d\", 0);\n    }\n    // +n*n (Technique 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            printf(\"%d\", 0);\n        }\n    }\n}\n
fun algorithm(n: Int) {\n    var a = 1   // +0 (Technique 1)\n    a = a + n   // +0 (Technique 1)\n    // +n (Technique 2)\n    for (i in 0..<5 * n + 1) {\n        println(0)\n    }\n    // +n*n (Technique 3)\n    for (i in 0..<2 * n) {\n        for (j in 0..<n + 1) {\n            println(0)\n        }\n    }\n}\n
def algorithm(n)\n    a = 1       # +0 (Technique 1)\n    a = a + n   # +0 (Technique 1)\n    # +n (Technique 2)\n    (0...(5 * n + 1)).each do { puts 0 }\n    # +n*n (Technique 3)\n    (0...(2 * n)).each do\n        (0...(n + 1)).each do { puts 0 }\n    end\nend\n

The following formula shows the counting results before and after using the above techniques; both derive a time complexity of \\(O(n^2)\\).

\\[ \\begin{aligned} T(n) & = 2n(n + 1) + (5n + 1) + 2 & \\text{Complete count (-.-|||)} \\newline & = 2n^2 + 7n + 3 \\newline T(n) & = n^2 + n & \\text{Simplified count (o.O)} \\end{aligned} \\]","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#2-step-2-determine-the-asymptotic-upper-bound","level":3,"title":"2.   Step 2: Determine the Asymptotic Upper Bound","text":"

Time complexity is determined by the highest-order term in \\(T(n)\\). This is because as \\(n\\) tends to infinity, the highest-order term will play a dominant role, and the influence of other terms can be ignored.

Table 2-2 shows some examples, where some exaggerated values are used to emphasize the conclusion that \"coefficients cannot shake the order\". When \\(n\\) tends to infinity, these constants become insignificant.

Table 2-2   Time complexities corresponding to different numbers of operations

Number of Operations \\(T(n)\\) Time Complexity \\(O(f(n))\\) \\(100000\\) \\(O(1)\\) \\(3n + 2\\) \\(O(n)\\) \\(2n^2 + 3n + 2\\) \\(O(n^2)\\) \\(n^3 + 10000n^2\\) \\(O(n^3)\\) \\(2^n + 10000n^{10000}\\) \\(O(2^n)\\)","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#234-common-types","level":2,"title":"2.3.4   Common Types","text":"

Let the input data size be \\(n\\). Common time complexity types are shown in Figure 2-9 (arranged in order from low to high).

\\[ \\begin{aligned} O(1) < O(\\log n) < O(n) < O(n \\log n) < O(n^2) < O(2^n) < O(n!) \\newline \\text{Constant order} < \\text{Logarithmic order} < \\text{Linear order} < \\text{Linearithmic order} < \\text{Quadratic order} < \\text{Exponential order} < \\text{Factorial order} \\end{aligned} \\]

Figure 2-9   Common time complexity types

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#1-constant-order-o1","level":3,"title":"1.   Constant Order \\(O(1)\\)","text":"

The number of operations in constant order is independent of the input data size \\(n\\), meaning it does not change as \\(n\\) changes.

In the following function, although the number of operations size may be large, since it is independent of the input data size \\(n\\), the time complexity remains \\(O(1)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def constant(n: int) -> int:\n    \"\"\"Constant order\"\"\"\n    count = 0\n    size = 100000\n    for _ in range(size):\n        count += 1\n    return count\n
time_complexity.cpp
/* Constant order */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
time_complexity.java
/* Constant order */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
time_complexity.cs
/* Constant order */\nint Constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
time_complexity.go
/* Constant order */\nfunc constant(n int) int {\n    count := 0\n    size := 100000\n    for i := 0; i < size; i++ {\n        count++\n    }\n    return count\n}\n
time_complexity.swift
/* Constant order */\nfunc constant(n: Int) -> Int {\n    var count = 0\n    let size = 100_000\n    for _ in 0 ..< size {\n        count += 1\n    }\n    return count\n}\n
time_complexity.js
/* Constant order */\nfunction constant(n) {\n    let count = 0;\n    const size = 100000;\n    for (let i = 0; i < size; i++) count++;\n    return count;\n}\n
time_complexity.ts
/* Constant order */\nfunction constant(n: number): number {\n    let count = 0;\n    const size = 100000;\n    for (let i = 0; i < size; i++) count++;\n    return count;\n}\n
time_complexity.dart
/* Constant order */\nint constant(int n) {\n  int count = 0;\n  int size = 100000;\n  for (var i = 0; i < size; i++) {\n    count++;\n  }\n  return count;\n}\n
time_complexity.rs
/* Constant order */\nfn constant(n: i32) -> i32 {\n    _ = n;\n    let mut count = 0;\n    let size = 100_000;\n    for _ in 0..size {\n        count += 1;\n    }\n    count\n}\n
time_complexity.c
/* Constant order */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    int i = 0;\n    for (int i = 0; i < size; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.kt
/* Constant order */\nfun constant(n: Int): Int {\n    var count = 0\n    val size = 100000\n    for (i in 0..<size)\n        count++\n    return count\n}\n
time_complexity.rb
### Constant time ###\ndef constant(n)\n  count = 0\n  size = 100000\n\n  (0...size).each { count += 1 }\n\n  count\nend\n
","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#2-linear-order-on","level":3,"title":"2.   Linear Order \\(O(n)\\)","text":"

The number of operations in linear order grows linearly relative to the input data size \\(n\\). Linear order typically appears in single-layer loops:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def linear(n: int) -> int:\n    \"\"\"Linear order\"\"\"\n    count = 0\n    for _ in range(n):\n        count += 1\n    return count\n
time_complexity.cpp
/* Linear order */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
time_complexity.java
/* Linear order */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
time_complexity.cs
/* Linear order */\nint Linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
time_complexity.go
/* Linear order */\nfunc linear(n int) int {\n    count := 0\n    for i := 0; i < n; i++ {\n        count++\n    }\n    return count\n}\n
time_complexity.swift
/* Linear order */\nfunc linear(n: Int) -> Int {\n    var count = 0\n    for _ in 0 ..< n {\n        count += 1\n    }\n    return count\n}\n
time_complexity.js
/* Linear order */\nfunction linear(n) {\n    let count = 0;\n    for (let i = 0; i < n; i++) count++;\n    return count;\n}\n
time_complexity.ts
/* Linear order */\nfunction linear(n: number): number {\n    let count = 0;\n    for (let i = 0; i < n; i++) count++;\n    return count;\n}\n
time_complexity.dart
/* Linear order */\nint linear(int n) {\n  int count = 0;\n  for (var i = 0; i < n; i++) {\n    count++;\n  }\n  return count;\n}\n
time_complexity.rs
/* Linear order */\nfn linear(n: i32) -> i32 {\n    let mut count = 0;\n    for _ in 0..n {\n        count += 1;\n    }\n    count\n}\n
time_complexity.c
/* Linear order */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.kt
/* Linear order */\nfun linear(n: Int): Int {\n    var count = 0\n    for (i in 0..<n)\n        count++\n    return count\n}\n
time_complexity.rb
### Linear time ###\ndef linear(n)\n  count = 0\n  (0...n).each { count += 1 }\n  count\nend\n

Operations such as traversing arrays and traversing linked lists have a time complexity of \\(O(n)\\), where \\(n\\) is the length of the array or linked list:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def array_traversal(nums: list[int]) -> int:\n    \"\"\"Linear order (traversing array)\"\"\"\n    count = 0\n    # Number of iterations is proportional to the array length\n    for num in nums:\n        count += 1\n    return count\n
time_complexity.cpp
/* Linear order (traversing array) */\nint arrayTraversal(vector<int> &nums) {\n    int count = 0;\n    // Number of iterations is proportional to the array length\n    for (int num : nums) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.java
/* Linear order (traversing array) */\nint arrayTraversal(int[] nums) {\n    int count = 0;\n    // Number of iterations is proportional to the array length\n    for (int num : nums) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.cs
/* Linear order (traversing array) */\nint ArrayTraversal(int[] nums) {\n    int count = 0;\n    // Number of iterations is proportional to the array length\n    foreach (int num in nums) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.go
/* Linear order (traversing array) */\nfunc arrayTraversal(nums []int) int {\n    count := 0\n    // Number of iterations is proportional to the array length\n    for range nums {\n        count++\n    }\n    return count\n}\n
time_complexity.swift
/* Linear order (traversing array) */\nfunc arrayTraversal(nums: [Int]) -> Int {\n    var count = 0\n    // Number of iterations is proportional to the array length\n    for _ in nums {\n        count += 1\n    }\n    return count\n}\n
time_complexity.js
/* Linear order (traversing array) */\nfunction arrayTraversal(nums) {\n    let count = 0;\n    // Number of iterations is proportional to the array length\n    for (let i = 0; i < nums.length; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.ts
/* Linear order (traversing array) */\nfunction arrayTraversal(nums: number[]): number {\n    let count = 0;\n    // Number of iterations is proportional to the array length\n    for (let i = 0; i < nums.length; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.dart
/* Linear order (traversing array) */\nint arrayTraversal(List<int> nums) {\n  int count = 0;\n  // Number of iterations is proportional to the array length\n  for (var _num in nums) {\n    count++;\n  }\n  return count;\n}\n
time_complexity.rs
/* Linear order (traversing array) */\nfn array_traversal(nums: &[i32]) -> i32 {\n    let mut count = 0;\n    // Number of iterations is proportional to the array length\n    for _ in nums {\n        count += 1;\n    }\n    count\n}\n
time_complexity.c
/* Linear order (traversing array) */\nint arrayTraversal(int *nums, int n) {\n    int count = 0;\n    // Number of iterations is proportional to the array length\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.kt
/* Linear order (traversing array) */\nfun arrayTraversal(nums: IntArray): Int {\n    var count = 0\n    // Number of iterations is proportional to the array length\n    for (num in nums) {\n        count++\n    }\n    return count\n}\n
time_complexity.rb
### Linear time (array traversal) ###\ndef array_traversal(nums)\n  count = 0\n\n  # Number of iterations is proportional to the array length\n  for num in nums\n    count += 1\n  end\n\n  count\nend\n

It is worth noting that the input data size \\(n\\) should be determined according to the type of input data. For example, in the first example, the variable \\(n\\) is the input data size; in the second example, the array length \\(n\\) is the data size.

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#3-quadratic-order-on2","level":3,"title":"3.   Quadratic Order \\(O(n^2)\\)","text":"

The number of operations in quadratic order grows quadratically relative to the input data size \\(n\\). Quadratic order typically appears in nested loops, where both the outer and inner loops have a time complexity of \\(O(n)\\), resulting in an overall time complexity of \\(O(n^2)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def quadratic(n: int) -> int:\n    \"\"\"Quadratic order\"\"\"\n    count = 0\n    # Number of iterations is quadratically related to the data size n\n    for i in range(n):\n        for j in range(n):\n            count += 1\n    return count\n
time_complexity.cpp
/* Exponential order */\nint quadratic(int n) {\n    int count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.java
/* Exponential order */\nint quadratic(int n) {\n    int count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.cs
/* Exponential order */\nint Quadratic(int n) {\n    int count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.go
/* Exponential order */\nfunc quadratic(n int) int {\n    count := 0\n    // Number of iterations is quadratically related to the data size n\n    for i := 0; i < n; i++ {\n        for j := 0; j < n; j++ {\n            count++\n        }\n    }\n    return count\n}\n
time_complexity.swift
/* Exponential order */\nfunc quadratic(n: Int) -> Int {\n    var count = 0\n    // Number of iterations is quadratically related to the data size n\n    for _ in 0 ..< n {\n        for _ in 0 ..< n {\n            count += 1\n        }\n    }\n    return count\n}\n
time_complexity.js
/* Exponential order */\nfunction quadratic(n) {\n    let count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.ts
/* Exponential order */\nfunction quadratic(n: number): number {\n    let count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.dart
/* Exponential order */\nint quadratic(int n) {\n  int count = 0;\n  // Number of iterations is quadratically related to the data size n\n  for (int i = 0; i < n; i++) {\n    for (int j = 0; j < n; j++) {\n      count++;\n    }\n  }\n  return count;\n}\n
time_complexity.rs
/* Exponential order */\nfn quadratic(n: i32) -> i32 {\n    let mut count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for _ in 0..n {\n        for _ in 0..n {\n            count += 1;\n        }\n    }\n    count\n}\n
time_complexity.c
/* Exponential order */\nint quadratic(int n) {\n    int count = 0;\n    // Number of iterations is quadratically related to the data size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
time_complexity.kt
/* Exponential order */\nfun quadratic(n: Int): Int {\n    var count = 0\n    // Number of iterations is quadratically related to the data size n\n    for (i in 0..<n) {\n        for (j in 0..<n) {\n            count++\n        }\n    }\n    return count\n}\n
time_complexity.rb
### Quadratic time ###\ndef quadratic(n)\n  count = 0\n\n  # Number of iterations is quadratically related to the data size n\n  for i in 0...n\n    for j in 0...n\n      count += 1\n    end\n  end\n\n  count\nend\n

Figure 2-10 compares constant order, linear order, and quadratic order time complexities.

Figure 2-10   Time complexities of constant, linear, and quadratic orders

Taking bubble sort as an example, the outer loop executes \\(n - 1\\) times, and the inner loop executes \\(n-1\\), \\(n-2\\), \\(\\dots\\), \\(2\\), \\(1\\) times, averaging \\(n / 2\\) times, resulting in a time complexity of \\(O((n - 1) n / 2) = O(n^2)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def bubble_sort(nums: list[int]) -> int:\n    \"\"\"Quadratic order (bubble sort)\"\"\"\n    count = 0  # Counter\n    # Outer loop: unsorted range is [0, i]\n    for i in range(len(nums) - 1, 0, -1):\n        # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                tmp: int = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = tmp\n                count += 3  # Element swap includes 3 unit operations\n    return count\n
time_complexity.cpp
/* Quadratic order (bubble sort) */\nint bubbleSort(vector<int> &nums) {\n    int count = 0; // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.java
/* Quadratic order (bubble sort) */\nint bubbleSort(int[] nums) {\n    int count = 0; // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.cs
/* Quadratic order (bubble sort) */\nint BubbleSort(int[] nums) {\n    int count = 0;  // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n                count += 3;  // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.go
/* Quadratic order (bubble sort) */\nfunc bubbleSort(nums []int) int {\n    count := 0 // Counter\n    // Outer loop: unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                tmp := nums[j]\n                nums[j] = nums[j+1]\n                nums[j+1] = tmp\n                count += 3 // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count\n}\n
time_complexity.swift
/* Quadratic order (bubble sort) */\nfunc bubbleSort(nums: inout [Int]) -> Int {\n    var count = 0 // Counter\n    // Outer loop: unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = tmp\n                count += 3 // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count\n}\n
time_complexity.js
/* Quadratic order (bubble sort) */\nfunction bubbleSort(nums) {\n    let count = 0; // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.ts
/* Quadratic order (bubble sort) */\nfunction bubbleSort(nums: number[]): number {\n    let count = 0; // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.dart
/* Quadratic order (bubble sort) */\nint bubbleSort(List<int> nums) {\n  int count = 0; // Counter\n  // Outer loop: unsorted range is [0, i]\n  for (var i = nums.length - 1; i > 0; i--) {\n    // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for (var j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n        count += 3; // Element swap includes 3 unit operations\n      }\n    }\n  }\n  return count;\n}\n
time_complexity.rs
/* Quadratic order (bubble sort) */\nfn bubble_sort(nums: &mut [i32]) -> i32 {\n    let mut count = 0; // Counter\n\n    // Outer loop: unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    count\n}\n
time_complexity.c
/* Quadratic order (bubble sort) */\nint bubbleSort(int *nums, int n) {\n    int count = 0; // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (int i = n - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count;\n}\n
time_complexity.kt
/* Quadratic order (bubble sort) */\nfun bubbleSort(nums: IntArray): Int {\n    var count = 0 // Counter\n    // Outer loop: unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n                count += 3 // Element swap includes 3 unit operations\n            }\n        }\n    }\n    return count\n}\n
time_complexity.rb
### Quadratic time (bubble sort) ###\ndef bubble_sort(nums)\n  count = 0  # Counter\n\n  # Outer loop: unsorted range is [0, i]\n  for i in (nums.length - 1).downto(0)\n    # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        tmp = nums[j]\n        nums[j] = nums[j + 1]\n        nums[j + 1] = tmp\n        count += 3 # Element swap includes 3 unit operations\n      end\n    end\n  end\n\n  count\nend\n
","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#4-exponential-order-o2n","level":3,"title":"4.   Exponential Order \\(O(2^n)\\)","text":"

Biological \"cell division\" is a typical example of exponential order growth: the initial state is \\(1\\) cell, after one round of division it becomes \\(2\\), after two rounds it becomes \\(4\\), and so on; after \\(n\\) rounds of division there are \\(2^n\\) cells.

Figure 2-11 and the following code simulate the cell division process, with a time complexity of \\(O(2^n)\\). Note that the input \\(n\\) represents the number of division rounds, and the return value count represents the total number of divisions.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def exponential(n: int) -> int:\n    \"\"\"Exponential order (loop implementation)\"\"\"\n    count = 0\n    base = 1\n    # Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in range(n):\n        for _ in range(base):\n            count += 1\n        base *= 2\n    # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n
time_complexity.cpp
/* Exponential order (loop implementation) */\nint exponential(int n) {\n    int count = 0, base = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.java
/* Exponential order (loop implementation) */\nint exponential(int n) {\n    int count = 0, base = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.cs
/* Exponential order (loop implementation) */\nint Exponential(int n) {\n    int count = 0, bas = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < bas; j++) {\n            count++;\n        }\n        bas *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.go
/* Exponential order (loop implementation) */\nfunc exponential(n int) int {\n    count, base := 0, 1\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for i := 0; i < n; i++ {\n        for j := 0; j < base; j++ {\n            count++\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
time_complexity.swift
/* Exponential order (loop implementation) */\nfunc exponential(n: Int) -> Int {\n    var count = 0\n    var base = 1\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in 0 ..< n {\n        for _ in 0 ..< base {\n            count += 1\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
time_complexity.js
/* Exponential order (loop implementation) */\nfunction exponential(n) {\n    let count = 0,\n        base = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.ts
/* Exponential order (loop implementation) */\nfunction exponential(n: number): number {\n    let count = 0,\n        base = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.dart
/* Exponential order (loop implementation) */\nint exponential(int n) {\n  int count = 0, base = 1;\n  // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n  for (var i = 0; i < n; i++) {\n    for (var j = 0; j < base; j++) {\n      count++;\n    }\n    base *= 2;\n  }\n  // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n  return count;\n}\n
time_complexity.rs
/* Exponential order (loop implementation) */\nfn exponential(n: i32) -> i32 {\n    let mut count = 0;\n    let mut base = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in 0..n {\n        for _ in 0..base {\n            count += 1\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    count\n}\n
time_complexity.c
/* Exponential order (loop implementation) */\nint exponential(int n) {\n    int count = 0;\n    int bas = 1;\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < bas; j++) {\n            count++;\n        }\n        bas *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
time_complexity.kt
/* Exponential order (loop implementation) */\nfun exponential(n: Int): Int {\n    var count = 0\n    var base = 1\n    // Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (i in 0..<n) {\n        for (j in 0..<base) {\n            count++\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
time_complexity.rb
### Exponential time (iterative) ###\ndef exponential(n)\n  count, base = 0, 1\n\n  # Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)\n  (0...n).each do\n    (0...base).each { count += 1 }\n    base *= 2\n  end\n\n  # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n  count\nend\n

Figure 2-11   Time complexity of exponential order

In actual algorithms, exponential order often appears in recursive functions. For example, in the following code, it recursively splits in two, stopping after \\(n\\) splits:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def exp_recur(n: int) -> int:\n    \"\"\"Exponential order (recursive implementation)\"\"\"\n    if n == 1:\n        return 1\n    return exp_recur(n - 1) + exp_recur(n - 1) + 1\n
time_complexity.cpp
/* Exponential order (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.java
/* Exponential order (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.cs
/* Exponential order (recursive implementation) */\nint ExpRecur(int n) {\n    if (n == 1) return 1;\n    return ExpRecur(n - 1) + ExpRecur(n - 1) + 1;\n}\n
time_complexity.go
/* Exponential order (recursive implementation) */\nfunc expRecur(n int) int {\n    if n == 1 {\n        return 1\n    }\n    return expRecur(n-1) + expRecur(n-1) + 1\n}\n
time_complexity.swift
/* Exponential order (recursive implementation) */\nfunc expRecur(n: Int) -> Int {\n    if n == 1 {\n        return 1\n    }\n    return expRecur(n: n - 1) + expRecur(n: n - 1) + 1\n}\n
time_complexity.js
/* Exponential order (recursive implementation) */\nfunction expRecur(n) {\n    if (n === 1) return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.ts
/* Exponential order (recursive implementation) */\nfunction expRecur(n: number): number {\n    if (n === 1) return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.dart
/* Exponential order (recursive implementation) */\nint expRecur(int n) {\n  if (n == 1) return 1;\n  return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.rs
/* Exponential order (recursive implementation) */\nfn exp_recur(n: i32) -> i32 {\n    if n == 1 {\n        return 1;\n    }\n    exp_recur(n - 1) + exp_recur(n - 1) + 1\n}\n
time_complexity.c
/* Exponential order (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
time_complexity.kt
/* Exponential order (recursive implementation) */\nfun expRecur(n: Int): Int {\n    if (n == 1) {\n        return 1\n    }\n    return expRecur(n - 1) + expRecur(n - 1) + 1\n}\n
time_complexity.rb
### Exponential time (recursive) ###\ndef exp_recur(n)\n  return 1 if n == 1\n  exp_recur(n - 1) + exp_recur(n - 1) + 1\nend\n

Exponential order growth is very rapid and is common in exhaustive methods (brute force search, backtracking, etc.). For problems with large data scales, exponential order is unacceptable and typically requires dynamic programming or greedy algorithms to solve.

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#5-logarithmic-order-olog-n","level":3,"title":"5.   Logarithmic Order \\(O(\\log n)\\)","text":"

In contrast to exponential order, logarithmic order reflects the situation of \"reducing to half each round\". Let the input data size be \\(n\\). Since it is reduced to half each round, the number of loops is \\(\\log_2 n\\), which is the inverse function of \\(2^n\\).

Figure 2-12 and the following code simulate the process of \"reducing to half each round\", with a time complexity of \\(O(\\log_2 n)\\), abbreviated as \\(O(\\log n)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def logarithmic(n: int) -> int:\n    \"\"\"Logarithmic order (loop implementation)\"\"\"\n    count = 0\n    while n > 1:\n        n = n / 2\n        count += 1\n    return count\n
time_complexity.cpp
/* Logarithmic order (loop implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.java
/* Logarithmic order (loop implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.cs
/* Logarithmic order (loop implementation) */\nint Logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n /= 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.go
/* Logarithmic order (loop implementation) */\nfunc logarithmic(n int) int {\n    count := 0\n    for n > 1 {\n        n = n / 2\n        count++\n    }\n    return count\n}\n
time_complexity.swift
/* Logarithmic order (loop implementation) */\nfunc logarithmic(n: Int) -> Int {\n    var count = 0\n    var n = n\n    while n > 1 {\n        n = n / 2\n        count += 1\n    }\n    return count\n}\n
time_complexity.js
/* Logarithmic order (loop implementation) */\nfunction logarithmic(n) {\n    let count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.ts
/* Logarithmic order (loop implementation) */\nfunction logarithmic(n: number): number {\n    let count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.dart
/* Logarithmic order (loop implementation) */\nint logarithmic(int n) {\n  int count = 0;\n  while (n > 1) {\n    n = n ~/ 2;\n    count++;\n  }\n  return count;\n}\n
time_complexity.rs
/* Logarithmic order (loop implementation) */\nfn logarithmic(mut n: i32) -> i32 {\n    let mut count = 0;\n    while n > 1 {\n        n = n / 2;\n        count += 1;\n    }\n    count\n}\n
time_complexity.c
/* Logarithmic order (loop implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
time_complexity.kt
/* Logarithmic order (loop implementation) */\nfun logarithmic(n: Int): Int {\n    var n1 = n\n    var count = 0\n    while (n1 > 1) {\n        n1 /= 2\n        count++\n    }\n    return count\n}\n
time_complexity.rb
### Logarithmic time (iterative) ###\ndef logarithmic(n)\n  count = 0\n\n  while n > 1\n    n /= 2\n    count += 1\n  end\n\n  count\nend\n

Figure 2-12   Time complexity of logarithmic order

Like exponential order, logarithmic order also commonly appears in recursive functions. The following code forms a recursion tree of height \\(\\log_2 n\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def log_recur(n: int) -> int:\n    \"\"\"Logarithmic order (recursive implementation)\"\"\"\n    if n <= 1:\n        return 0\n    return log_recur(n / 2) + 1\n
time_complexity.cpp
/* Logarithmic order (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
time_complexity.java
/* Logarithmic order (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
time_complexity.cs
/* Logarithmic order (recursive implementation) */\nint LogRecur(int n) {\n    if (n <= 1) return 0;\n    return LogRecur(n / 2) + 1;\n}\n
time_complexity.go
/* Logarithmic order (recursive implementation) */\nfunc logRecur(n int) int {\n    if n <= 1 {\n        return 0\n    }\n    return logRecur(n/2) + 1\n}\n
time_complexity.swift
/* Logarithmic order (recursive implementation) */\nfunc logRecur(n: Int) -> Int {\n    if n <= 1 {\n        return 0\n    }\n    return logRecur(n: n / 2) + 1\n}\n
time_complexity.js
/* Logarithmic order (recursive implementation) */\nfunction logRecur(n) {\n    if (n <= 1) return 0;\n    return logRecur(n / 2) + 1;\n}\n
time_complexity.ts
/* Logarithmic order (recursive implementation) */\nfunction logRecur(n: number): number {\n    if (n <= 1) return 0;\n    return logRecur(n / 2) + 1;\n}\n
time_complexity.dart
/* Logarithmic order (recursive implementation) */\nint logRecur(int n) {\n  if (n <= 1) return 0;\n  return logRecur(n ~/ 2) + 1;\n}\n
time_complexity.rs
/* Logarithmic order (recursive implementation) */\nfn log_recur(n: i32) -> i32 {\n    if n <= 1 {\n        return 0;\n    }\n    log_recur(n / 2) + 1\n}\n
time_complexity.c
/* Logarithmic order (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
time_complexity.kt
/* Logarithmic order (recursive implementation) */\nfun logRecur(n: Int): Int {\n    if (n <= 1)\n        return 0\n    return logRecur(n / 2) + 1\n}\n
time_complexity.rb
### Logarithmic time (recursive) ###\ndef log_recur(n)\n  return 0 unless n > 1\n  log_recur(n / 2) + 1\nend\n

Logarithmic order commonly appears in algorithms based on the divide-and-conquer strategy, embodying the algorithmic thinking of \"dividing into many\" and \"simplifying complexity\". It grows slowly and is the ideal time complexity second only to constant order.

What is the base of \\(O(\\log n)\\)?

To be precise, \"dividing into \\(m\\)\" corresponds to a time complexity of \\(O(\\log_m n)\\). And through the logarithmic base change formula, we can obtain time complexities with different bases that are equal:

\\[ O(\\log_m n) = O(\\log_k n / \\log_k m) = O(\\log_k n) \\]

That is to say, the base \\(m\\) can be converted without affecting the complexity. Therefore, we usually omit the base \\(m\\) and denote logarithmic order simply as \\(O(\\log n)\\).

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#6-linearithmic-order-on-log-n","level":3,"title":"6.   Linearithmic Order \\(O(n \\log n)\\)","text":"

Linearithmic order commonly appears in nested loops, where the time complexities of the two layers of loops are \\(O(\\log n)\\) and \\(O(n)\\) respectively. The relevant code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def linear_log_recur(n: int) -> int:\n    \"\"\"Linearithmic order\"\"\"\n    if n <= 1:\n        return 1\n    # Divide into two, the scale of subproblems is reduced by half\n    count = linear_log_recur(n // 2) + linear_log_recur(n // 2)\n    # Current subproblem contains n operations\n    for _ in range(n):\n        count += 1\n    return count\n
time_complexity.cpp
/* Linearithmic order */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.java
/* Linearithmic order */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.cs
/* Linearithmic order */\nint LinearLogRecur(int n) {\n    if (n <= 1) return 1;\n    int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.go
/* Linearithmic order */\nfunc linearLogRecur(n int) int {\n    if n <= 1 {\n        return 1\n    }\n    count := linearLogRecur(n/2) + linearLogRecur(n/2)\n    for i := 0; i < n; i++ {\n        count++\n    }\n    return count\n}\n
time_complexity.swift
/* Linearithmic order */\nfunc linearLogRecur(n: Int) -> Int {\n    if n <= 1 {\n        return 1\n    }\n    var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2)\n    for _ in stride(from: 0, to: n, by: 1) {\n        count += 1\n    }\n    return count\n}\n
time_complexity.js
/* Linearithmic order */\nfunction linearLogRecur(n) {\n    if (n <= 1) return 1;\n    let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (let i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.ts
/* Linearithmic order */\nfunction linearLogRecur(n: number): number {\n    if (n <= 1) return 1;\n    let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (let i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.dart
/* Linearithmic order */\nint linearLogRecur(int n) {\n  if (n <= 1) return 1;\n  int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2);\n  for (var i = 0; i < n; i++) {\n    count++;\n  }\n  return count;\n}\n
time_complexity.rs
/* Linearithmic order */\nfn linear_log_recur(n: i32) -> i32 {\n    if n <= 1 {\n        return 1;\n    }\n    let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2);\n    for _ in 0..n {\n        count += 1;\n    }\n    return count;\n}\n
time_complexity.c
/* Linearithmic order */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
time_complexity.kt
/* Linearithmic order */\nfun linearLogRecur(n: Int): Int {\n    if (n <= 1)\n        return 1\n    var count = linearLogRecur(n / 2) + linearLogRecur(n / 2)\n    for (i in 0..<n) {\n        count++\n    }\n    return count\n}\n
time_complexity.rb
### Linearithmic time ###\ndef linear_log_recur(n)\n  return 1 unless n > 1\n\n  count = linear_log_recur(n / 2) + linear_log_recur(n / 2)\n  (0...n).each { count += 1 }\n\n  count\nend\n

Figure 2-13 shows how linearithmic order is generated. Each level of the binary tree has a total of \\(n\\) operations, and the tree has \\(\\log_2 n + 1\\) levels, resulting in a time complexity of \\(O(n \\log n)\\).

Figure 2-13   Time complexity of linearithmic order

Mainstream sorting algorithms typically have a time complexity of \\(O(n \\log n)\\), such as quicksort, merge sort, and heap sort.

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#7-factorial-order-on","level":3,"title":"7.   Factorial Order \\(O(n!)\\)","text":"

Factorial order corresponds to the mathematical \"permutation\" problem. Given \\(n\\) distinct elements, find all possible permutation schemes; the number of schemes is:

\\[ n! = n \\times (n - 1) \\times (n - 2) \\times \\dots \\times 2 \\times 1 \\]

Factorials are typically implemented using recursion. As shown in Figure 2-14 and the following code, the first level splits into \\(n\\) branches, the second level splits into \\(n - 1\\) branches, and so on, until the \\(n\\)-th level when splitting stops:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
def factorial_recur(n: int) -> int:\n    \"\"\"Factorial order (recursive implementation)\"\"\"\n    if n == 0:\n        return 1\n    count = 0\n    # Split from 1 into n\n    for _ in range(n):\n        count += factorial_recur(n - 1)\n    return count\n
time_complexity.cpp
/* Factorial order (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    // Split from 1 into n\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.java
/* Factorial order (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    // Split from 1 into n\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.cs
/* Factorial order (recursive implementation) */\nint FactorialRecur(int n) {\n    if (n == 0) return 1;\n    int count = 0;\n    // Split from 1 into n\n    for (int i = 0; i < n; i++) {\n        count += FactorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.go
/* Factorial order (recursive implementation) */\nfunc factorialRecur(n int) int {\n    if n == 0 {\n        return 1\n    }\n    count := 0\n    // Split from 1 into n\n    for i := 0; i < n; i++ {\n        count += factorialRecur(n - 1)\n    }\n    return count\n}\n
time_complexity.swift
/* Factorial order (recursive implementation) */\nfunc factorialRecur(n: Int) -> Int {\n    if n == 0 {\n        return 1\n    }\n    var count = 0\n    // Split from 1 into n\n    for _ in 0 ..< n {\n        count += factorialRecur(n: n - 1)\n    }\n    return count\n}\n
time_complexity.js
/* Factorial order (recursive implementation) */\nfunction factorialRecur(n) {\n    if (n === 0) return 1;\n    let count = 0;\n    // Split from 1 into n\n    for (let i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.ts
/* Factorial order (recursive implementation) */\nfunction factorialRecur(n: number): number {\n    if (n === 0) return 1;\n    let count = 0;\n    // Split from 1 into n\n    for (let i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.dart
/* Factorial order (recursive implementation) */\nint factorialRecur(int n) {\n  if (n == 0) return 1;\n  int count = 0;\n  // Split from 1 into n\n  for (var i = 0; i < n; i++) {\n    count += factorialRecur(n - 1);\n  }\n  return count;\n}\n
time_complexity.rs
/* Factorial order (recursive implementation) */\nfn factorial_recur(n: i32) -> i32 {\n    if n == 0 {\n        return 1;\n    }\n    let mut count = 0;\n    // Split from 1 into n\n    for _ in 0..n {\n        count += factorial_recur(n - 1);\n    }\n    count\n}\n
time_complexity.c
/* Factorial order (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
time_complexity.kt
/* Factorial order (recursive implementation) */\nfun factorialRecur(n: Int): Int {\n    if (n == 0)\n        return 1\n    var count = 0\n    // Split from 1 into n\n    for (i in 0..<n) {\n        count += factorialRecur(n - 1)\n    }\n    return count\n}\n
time_complexity.rb
### Factorial time (recursive) ###\ndef factorial_recur(n)\n  return 1 if n == 0\n\n  count = 0\n  # Split from 1 into n\n  (0...n).each { count += factorial_recur(n - 1) }\n\n  count\nend\n

Figure 2-14   Time complexity of factorial order

Note that because when \\(n \\geq 4\\) we always have \\(n! > 2^n\\), factorial order grows faster than exponential order, and is also unacceptable for large \\(n\\).

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#235-worst-best-and-average-time-complexities","level":2,"title":"2.3.5   Worst, Best, and Average Time Complexities","text":"

The time efficiency of an algorithm is often not fixed, but is related to the distribution of the input data. Suppose we input an array nums of length \\(n\\), where nums consists of numbers from \\(1\\) to \\(n\\), with each number appearing only once, but the element order is randomly shuffled. The task is to return the index of element \\(1\\). We can draw the following conclusions.

  • When nums = [?, ?, ..., 1], i.e., when the last element is \\(1\\), it requires a complete traversal of the array, reaching worst-case time complexity \\(O(n)\\).
  • When nums = [1, ?, ?, ...], i.e., when the first element is \\(1\\), no matter how long the array is, there is no need to continue traversing, reaching best-case time complexity \\(\\Omega(1)\\).

The \"worst-case time complexity\" corresponds to the function's asymptotic upper bound, denoted using big-\\(O\\) notation. Correspondingly, the \"best-case time complexity\" corresponds to the function's asymptotic lower bound, denoted using \\(\\Omega\\) notation:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby worst_best_time_complexity.py
def random_numbers(n: int) -> list[int]:\n    \"\"\"Generate an array with elements: 1, 2, ..., n, shuffled in order\"\"\"\n    # Generate array nums =: 1, 2, 3, ..., n\n    nums = [i for i in range(1, n + 1)]\n    # Randomly shuffle array elements\n    random.shuffle(nums)\n    return nums\n\ndef find_one(nums: list[int]) -> int:\n    \"\"\"Find the index of number 1 in array nums\"\"\"\n    for i in range(len(nums)):\n        # When element 1 is at the head of the array, best time complexity O(1) is achieved\n        # When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if nums[i] == 1:\n            return i\n    return -1\n
worst_best_time_complexity.cpp
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nvector<int> randomNumbers(int n) {\n    vector<int> nums(n);\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Use system time to generate random seed\n    unsigned seed = chrono::system_clock::now().time_since_epoch().count();\n    // Randomly shuffle array elements\n    shuffle(nums.begin(), nums.end(), default_random_engine(seed));\n    return nums;\n}\n\n/* Find the index of number 1 in array nums */\nint findOne(vector<int> &nums) {\n    for (int i = 0; i < nums.size(); i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
worst_best_time_complexity.java
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nint[] randomNumbers(int n) {\n    Integer[] nums = new Integer[n];\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle array elements\n    Collections.shuffle(Arrays.asList(nums));\n    // Integer[] -> int[]\n    int[] res = new int[n];\n    for (int i = 0; i < n; i++) {\n        res[i] = nums[i];\n    }\n    return res;\n}\n\n/* Find the index of number 1 in array nums */\nint findOne(int[] nums) {\n    for (int i = 0; i < nums.length; i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
worst_best_time_complexity.cs
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nint[] RandomNumbers(int n) {\n    int[] nums = new int[n];\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n\n    // Randomly shuffle array elements\n    for (int i = 0; i < nums.Length; i++) {\n        int index = new Random().Next(i, nums.Length);\n        (nums[i], nums[index]) = (nums[index], nums[i]);\n    }\n    return nums;\n}\n\n/* Find the index of number 1 in array nums */\nint FindOne(int[] nums) {\n    for (int i = 0; i < nums.Length; i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
worst_best_time_complexity.go
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfunc randomNumbers(n int) []int {\n    nums := make([]int, n)\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for i := 0; i < n; i++ {\n        nums[i] = i + 1\n    }\n    // Randomly shuffle array elements\n    rand.Shuffle(len(nums), func(i, j int) {\n        nums[i], nums[j] = nums[j], nums[i]\n    })\n    return nums\n}\n\n/* Find the index of number 1 in array nums */\nfunc findOne(nums []int) int {\n    for i := 0; i < len(nums); i++ {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if nums[i] == 1 {\n            return i\n        }\n    }\n    return -1\n}\n
worst_best_time_complexity.swift
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfunc randomNumbers(n: Int) -> [Int] {\n    // Generate array nums = { 1, 2, 3, ..., n }\n    var nums = Array(1 ... n)\n    // Randomly shuffle array elements\n    nums.shuffle()\n    return nums\n}\n\n/* Find the index of number 1 in array nums */\nfunc findOne(nums: [Int]) -> Int {\n    for i in nums.indices {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if nums[i] == 1 {\n            return i\n        }\n    }\n    return -1\n}\n
worst_best_time_complexity.js
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfunction randomNumbers(n) {\n    const nums = Array(n);\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (let i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle array elements\n    for (let i = 0; i < n; i++) {\n        const r = Math.floor(Math.random() * (i + 1));\n        const temp = nums[i];\n        nums[i] = nums[r];\n        nums[r] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of number 1 in array nums */\nfunction findOne(nums) {\n    for (let i = 0; i < nums.length; i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] === 1) {\n            return i;\n        }\n    }\n    return -1;\n}\n
worst_best_time_complexity.ts
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfunction randomNumbers(n: number): number[] {\n    const nums = Array(n);\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (let i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle array elements\n    for (let i = 0; i < n; i++) {\n        const r = Math.floor(Math.random() * (i + 1));\n        const temp = nums[i];\n        nums[i] = nums[r];\n        nums[r] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of number 1 in array nums */\nfunction findOne(nums: number[]): number {\n    for (let i = 0; i < nums.length; i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] === 1) {\n            return i;\n        }\n    }\n    return -1;\n}\n
worst_best_time_complexity.dart
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nList<int> randomNumbers(int n) {\n  final nums = List.filled(n, 0);\n  // Generate array nums = { 1, 2, 3, ..., n }\n  for (var i = 0; i < n; i++) {\n    nums[i] = i + 1;\n  }\n  // Randomly shuffle array elements\n  nums.shuffle();\n\n  return nums;\n}\n\n/* Find the index of number 1 in array nums */\nint findOne(List<int> nums) {\n  for (var i = 0; i < nums.length; i++) {\n    // When element 1 is at the head of the array, best time complexity O(1) is achieved\n    // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n    if (nums[i] == 1) return i;\n  }\n\n  return -1;\n}\n
worst_best_time_complexity.rs
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfn random_numbers(n: i32) -> Vec<i32> {\n    // Generate array nums = { 1, 2, 3, ..., n }\n    let mut nums = (1..=n).collect::<Vec<i32>>();\n    // Randomly shuffle array elements\n    nums.shuffle(&mut thread_rng());\n    nums\n}\n\n/* Find the index of number 1 in array nums */\nfn find_one(nums: &[i32]) -> Option<usize> {\n    for i in 0..nums.len() {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if nums[i] == 1 {\n            return Some(i);\n        }\n    }\n    None\n}\n
worst_best_time_complexity.c
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nint *randomNumbers(int n) {\n    // Allocate heap memory (create 1D variable-length array: n elements of type int)\n    int *nums = (int *)malloc(n * sizeof(int));\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle array elements\n    for (int i = n - 1; i > 0; i--) {\n        int j = rand() % (i + 1);\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of number 1 in array nums */\nint findOne(int *nums, int n) {\n    for (int i = 0; i < n; i++) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
worst_best_time_complexity.kt
/* Generate an array with elements { 1, 2, ..., n }, order shuffled */\nfun randomNumbers(n: Int): Array<Int?> {\n    val nums = IntArray(n)\n    // Generate array nums = { 1, 2, 3, ..., n }\n    for (i in 0..<n) {\n        nums[i] = i + 1\n    }\n    // Randomly shuffle array elements\n    nums.shuffle()\n    val res = arrayOfNulls<Int>(n)\n    for (i in 0..<n) {\n        res[i] = nums[i]\n    }\n    return res\n}\n\n/* Find the index of number 1 in array nums */\nfun findOne(nums: Array<Int?>): Int {\n    for (i in nums.indices) {\n        // When element 1 is at the head of the array, best time complexity O(1) is achieved\n        // When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n        if (nums[i] == 1)\n            return i\n    }\n    return -1\n}\n
worst_best_time_complexity.rb
### Generate array with elements: 1, 2, ..., n, shuffled ###\ndef random_numbers(n)\n  # Generate array nums =: 1, 2, 3, ..., n\n  nums = Array.new(n) { |i| i + 1 }\n  # Randomly shuffle array elements\n  nums.shuffle!\nend\n\n### Find index of number 1 in array nums ###\ndef find_one(nums)\n  for i in 0...nums.length\n    # When element 1 is at the head of the array, best time complexity O(1) is achieved\n    # When element 1 is at the tail of the array, worst time complexity O(n) is achieved\n    return i if nums[i] == 1\n  end\n\n  -1\nend\n

It is worth noting that we rarely use best-case time complexity in practice, because it can usually only be achieved with a very small probability and may be somewhat misleading. The worst-case time complexity is more practical because it gives a safety value for efficiency, allowing us to use the algorithm with confidence.

From the above example, we can see that both worst-case and best-case time complexities only occur under \"special data distributions\", which may have a very small probability of occurrence and may not truly reflect the algorithm's running efficiency. In contrast, average time complexity can reflect the algorithm's running efficiency under random input data, denoted using the \\(\\Theta\\) notation.

For some algorithms, we can simply derive the average case under random data distribution. For example, in the above example, since the input array is shuffled, the probability of element \\(1\\) appearing at any index is equal, so the algorithm's average number of loops is half the array length \\(n / 2\\), giving an average time complexity of \\(\\Theta(n / 2) = \\Theta(n)\\).

But for more complex algorithms, calculating average time complexity is often quite difficult, because it is hard to analyze the overall mathematical expectation under data distribution. In this case, we usually use worst-case time complexity as the criterion for judging algorithm efficiency.

Why is the \\(\\Theta\\) symbol rarely seen?

This may be because the \\(O\\) symbol is too catchy, so we often use it to represent average time complexity. But strictly speaking, this practice is not standard. In this book and other materials, if you encounter expressions like \"average time complexity \\(O(n)\\)\", please understand it directly as \\(\\Theta(n)\\).

","path":["Chapter 2. Complexity Analysis","2.3   Time Complexity"],"tags":[]},{"location":"chapter_data_structure/","level":1,"title":"Chapter 3.   Data Structures","text":"

Abstract

Data structure is like a sturdy and diverse framework.

It provides a blueprint for the orderly organization of data, upon which algorithms come to life.

","path":["Chapter 3. Data Structures","Chapter 3.   Data Structures"],"tags":[]},{"location":"chapter_data_structure/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 3.1   Classification of Data Structures
  • 3.2   Basic Data Types
  • 3.3   Number Encoding *
  • 3.4   Character Encoding *
  • 3.5   Summary
","path":["Chapter 3. Data Structures","Chapter 3.   Data Structures"],"tags":[]},{"location":"chapter_data_structure/basic_data_types/","level":1,"title":"3.2   Basic Data Types","text":"

When we talk about data in computers, we think of various forms such as text, images, videos, audio, 3D models, and more. Although these data are organized in different ways, they are all composed of various basic data types.

Basic data types are types that the CPU can directly operate on, and they are directly used in algorithms, mainly including the following.

  • Integer types byte, short, int, long.
  • Floating-point types float, double, used to represent decimal numbers.
  • Character type char, used to represent letters, punctuation marks, and even emojis in various languages.
  • Boolean type bool, used to represent \"yes\" and \"no\" judgments.

Basic data types are stored in binary form in computers. One binary bit is \\(1\\) bit. In most modern operating systems, \\(1\\) byte consists of \\(8\\) bits.

The range of values for basic data types depends on the size of the space they occupy. Below is an example using Java.

  • Integer type byte occupies \\(1\\) byte = \\(8\\) bits, and can represent \\(2^{8}\\) numbers.
  • Integer type int occupies \\(4\\) bytes = \\(32\\) bits, and can represent \\(2^{32}\\) numbers.

The following table lists the space occupied, value ranges, and default values of various basic data types in Java. You don't need to memorize this table; a general understanding is sufficient, and you can refer to it when needed.

Table 3-1   Space occupied and value ranges of basic data types

Type Symbol Space Occupied Minimum Value Maximum Value Default Value Integer byte 1 byte \\(-2^7\\) (\\(-128\\)) \\(2^7 - 1\\) (\\(127\\)) \\(0\\) short 2 bytes \\(-2^{15}\\) \\(2^{15} - 1\\) \\(0\\) int 4 bytes \\(-2^{31}\\) \\(2^{31} - 1\\) \\(0\\) long 8 bytes \\(-2^{63}\\) \\(2^{63} - 1\\) \\(0\\) Float float 4 bytes \\(1.175 \\times 10^{-38}\\) \\(3.403 \\times 10^{38}\\) \\(0.0\\text{f}\\) double 8 bytes \\(2.225 \\times 10^{-308}\\) \\(1.798 \\times 10^{308}\\) \\(0.0\\) Character char 2 bytes \\(0\\) \\(2^{16} - 1\\) \\(0\\) Boolean bool 1 byte \\(\\text{false}\\) \\(\\text{true}\\) \\(\\text{false}\\)

Please note that the above table is specific to Java's basic data types. Each programming language has its own data type definitions, and their space occupied, value ranges, and default values may vary.

  • In Python, the integer type int can be of any size, limited only by available memory; the floating-point type float is double-precision 64-bit; there is no char type, a single character is actually a string str of length 1.
  • C and C++ do not explicitly specify the size of basic data types, which varies by implementation and platform. The above table follows the LP64 data model, which is used in Unix 64-bit operating systems including Linux and macOS.
  • The size of character char is 1 byte in C and C++, and in most programming languages it depends on the specific character encoding method, as detailed in the \"Character Encoding\" section.
  • Even though representing a boolean value requires only 1 bit (\\(0\\) or \\(1\\)), it is usually stored as 1 byte in memory. This is because modern computer CPUs typically use 1 byte as the minimum addressable memory unit.

So, what is the relationship between basic data types and data structures? We know that data structures are ways of organizing and storing data in computers. The subject of this statement is \"structure\", not \"data\".

If we want to represent \"a row of numbers\", we naturally think of using an array. This is because the linear structure of an array can represent the adjacency and order relationships of numbers, but the content stored—whether integer int, floating-point float, or character char—is unrelated to the \"data structure\".

In other words, basic data types provide the \"content type\" of data, while data structures provide the \"organization method\" of data. For example, in the following code, we use the same data structure (array) to store and represent different basic data types, including int, float, char, bool, etc.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
# Initialize arrays using various basic data types\nnumbers: list[int] = [0] * 5\ndecimals: list[float] = [0.0] * 5\n# In Python, characters are actually strings of length 1\ncharacters: list[str] = ['0'] * 5\nbools: list[bool] = [False] * 5\n# Python lists can freely store various basic data types and object references\ndata = [0, 0.0, 'a', False, ListNode(0)]\n
// Initialize arrays using various basic data types\nint numbers[5];\nfloat decimals[5];\nchar characters[5];\nbool bools[5];\n
// Initialize arrays using various basic data types\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nboolean[] bools = new boolean[5];\n
// Initialize arrays using various basic data types\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nbool[] bools = new bool[5];\n
// Initialize arrays using various basic data types\nvar numbers = [5]int{}\nvar decimals = [5]float64{}\nvar characters = [5]byte{}\nvar bools = [5]bool{}\n
// Initialize arrays using various basic data types\nlet numbers = Array(repeating: 0, count: 5)\nlet decimals = Array(repeating: 0.0, count: 5)\nlet characters: [Character] = Array(repeating: \"a\", count: 5)\nlet bools = Array(repeating: false, count: 5)\n
// JavaScript arrays can freely store various basic data types and objects\nconst array = [0, 0.0, 'a', false];\n
// Initialize arrays using various basic data types\nconst numbers: number[] = [];\nconst characters: string[] = [];\nconst bools: boolean[] = [];\n
// Initialize arrays using various basic data types\nList<int> numbers = List.filled(5, 0);\nList<double> decimals = List.filled(5, 0.0);\nList<String> characters = List.filled(5, 'a');\nList<bool> bools = List.filled(5, false);\n
// Initialize arrays using various basic data types\nlet numbers: Vec<i32> = vec![0; 5];\nlet decimals: Vec<f32> = vec![0.0; 5];\nlet characters: Vec<char> = vec!['0'; 5];\nlet bools: Vec<bool> = vec![false; 5];\n
// Initialize arrays using various basic data types\nint numbers[10];\nfloat decimals[10];\nchar characters[10];\nbool bools[10];\n
// Initialize arrays using various basic data types\nval numbers = IntArray(5)\nval decinals = FloatArray(5)\nval characters = CharArray(5)\nval bools = BooleanArray(5)\n
# Ruby lists can freely store various basic data types and object references\ndata = [0, 0.0, 'a', false, ListNode(0)]\n
Visualized Execution

https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%9D%A5%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E7%AC%A6%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%AD%98%E5%82%A8%E5%90%84%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

","path":["Chapter 3. Data Structures","3.2   Basic Data Types"],"tags":[]},{"location":"chapter_data_structure/character_encoding/","level":1,"title":"3.4   Character Encoding *","text":"

In computers, all data is stored in binary form, and character char is no exception. To represent characters, we need to establish a \"character set\" that defines a one-to-one correspondence between each character and binary numbers. With a character set, computers can convert binary numbers to characters by looking up the table.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#341-ascii-character-set","level":2,"title":"3.4.1   Ascii Character Set","text":"

ASCII code is the earliest character set, with the full name American Standard Code for Information Interchange. It uses 7 binary bits (the lower 7 bits of one byte) to represent a character, and can represent a maximum of 128 different characters. As shown in Figure 3-6, ASCII code includes uppercase and lowercase English letters, numbers 0 ~ 9, some punctuation marks, and some control characters (such as newline and tab).

Figure 3-6   ASCII code

However, ASCII code can only represent English. With the globalization of computers, a character set called EASCII that can represent more languages emerged. It expands from the 7-bit basis of ASCII to 8 bits, and can represent 256 different characters.

Worldwide, a batch of EASCII character sets suitable for different regions have appeared successively. The first 128 characters of these character sets are unified as ASCII code, and the last 128 characters are defined differently to adapt to the needs of different languages.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#342-gbk-character-set","level":2,"title":"3.4.2   Gbk Character Set","text":"

Later, people found that EASCII code still cannot meet the character quantity requirements of many languages. For example, there are nearly one hundred thousand Chinese characters, and several thousand are used daily. In 1980, the China National Standardization Administration released the GB2312 character set, which included 6,763 Chinese characters, basically meeting the needs for computer processing of Chinese characters.

However, GB2312 cannot handle some rare characters and traditional Chinese characters. The GBK character set is an extension based on GB2312, which includes a total of 21,886 Chinese characters. In the GBK encoding scheme, ASCII characters are represented using one byte, and Chinese characters are represented using two bytes.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#343-unicode-character-set","level":2,"title":"3.4.3   Unicode Character Set","text":"

With the vigorous development of computer technology, character sets and encoding standards flourished, which brought many problems. On the one hand, these character sets generally only define characters for specific languages and cannot work normally in multilingual environments. On the other hand, multiple character set standards exist for the same language, and if two computers use different encoding standards, garbled characters will appear during information transmission.

Researchers of that era thought: If a sufficiently complete character set is released that includes all languages and symbols in the world, wouldn't it be possible to solve cross-language environment and garbled character problems? Driven by this idea, a large and comprehensive character set, Unicode, was born.

Unicode is called \"统一码\" (Unified Code) in Chinese and can theoretically accommodate over one million characters. It is committed to including characters from around the world into a unified character set, providing a universal character set to handle and display various language texts, reducing garbled character problems caused by different encoding standards.

Since its release in 1991, Unicode has continuously expanded to include new languages and characters. As of September 2022, Unicode has included 149,186 characters, including characters, symbols, and even emojis from various languages. In the vast Unicode character set, commonly used characters occupy 2 bytes, and some rare characters occupy 3 bytes or even 4 bytes.

Unicode is a universal character set that essentially assigns a number (called a \"code point\") to each character, but it does not specify how to store these character code points in computers. We can't help but ask: when Unicode code points of multiple lengths appear simultaneously in a text, how does the system parse the characters? For example, given an encoding with a length of 2 bytes, how does the system determine whether it is one 2-byte character or two 1-byte characters?

For the above problem, a straightforward solution is to store all characters as equal-length encodings. As shown in Figure 3-7, each character in \"Hello\" occupies 1 byte, and each character in \"算法\" (algorithm) occupies 2 bytes. We can encode all characters in \"Hello 算法\" as 2 bytes in length by padding the high bits with 0. In this way, the system can parse one character every 2 bytes and restore the content of this phrase.

Figure 3-7   Unicode encoding example

However, ASCII code has already proven to us that encoding English only requires 1 byte. If the above scheme is adopted, the size of English text will be twice that under ASCII encoding, which is very wasteful of memory space. Therefore, we need a more efficient Unicode encoding method.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#344-utf-8-encoding","level":2,"title":"3.4.4   Utf-8 Encoding","text":"

Currently, UTF-8 has become the most widely used Unicode encoding method internationally. It is a variable-length encoding that uses 1 to 4 bytes to represent a character, depending on the complexity of the character. ASCII characters only require 1 byte, Latin and Greek letters require 2 bytes, commonly used Chinese characters require 3 bytes, and some other rare characters require 4 bytes.

The encoding rules of UTF-8 are not complicated and can be divided into the following two cases.

  • For 1-byte characters, set the highest bit to \\(0\\), and set the remaining 7 bits to the Unicode code point. It is worth noting that ASCII characters occupy the first 128 code points in the Unicode character set. That is to say, UTF-8 encoding is backward compatible with ASCII code. This means we can use UTF-8 to parse very old ASCII code text.
  • For characters with a length of \\(n\\) bytes (where \\(n > 1\\)), set the highest \\(n\\) bits of the first byte to \\(1\\), and set the \\((n + 1)\\)-th bit to \\(0\\); starting from the second byte, set the highest 2 bits of each byte to \\(10\\); use all remaining bits to fill in the Unicode code point of the character.

Figure 3-8 shows the UTF-8 encoding corresponding to \"Hello算法\". It can be observed that since the highest \\(n\\) bits are all set to \\(1\\), the system can parse the length of the character as \\(n\\) by reading the number of highest bits that are \\(1\\).

But why set the highest 2 bits of all other bytes to \\(10\\)? In fact, this \\(10\\) can serve as a check symbol. Assuming the system starts parsing text from an incorrect byte, the \\(10\\) at the beginning of the byte can help the system quickly determine an anomaly.

The reason for using \\(10\\) as a check symbol is that under UTF-8 encoding rules, it is impossible for a character's highest two bits to be \\(10\\). This conclusion can be proven by contradiction: assuming the highest two bits of a character are \\(10\\), it means the length of the character is \\(1\\), corresponding to ASCII code. However, the highest bit of ASCII code should be \\(0\\), which contradicts the assumption.

Figure 3-8   UTF-8 encoding example

In addition to UTF-8, common encoding methods also include the following two.

  • UTF-16 encoding: Uses 2 or 4 bytes to represent a character. All ASCII characters and commonly used non-English characters are represented with 2 bytes; a few characters need to use 4 bytes. For 2-byte characters, UTF-16 encoding is equal to the Unicode code point.
  • UTF-32 encoding: Every character uses 4 bytes. This means that UTF-32 takes up more space than UTF-8 and UTF-16, especially for text with a high proportion of ASCII characters.

From the perspective of storage space occupation, using UTF-8 to represent English characters is very efficient because it only requires 1 byte; using UTF-16 encoding for some non-English characters (such as Chinese) will be more efficient because it only requires 2 bytes, while UTF-8 may require 3 bytes.

From a compatibility perspective, UTF-8 has the best universality, and many tools and libraries support UTF-8 first.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#345-character-encoding-in-programming-languages","level":2,"title":"3.4.5   Character Encoding in Programming Languages","text":"

For most past programming languages, strings during program execution use fixed-length encodings such as UTF-16 or UTF-32. Under fixed-length encoding, we can treat strings as arrays for processing, and this approach has the following advantages.

  • Random access: UTF-16 encoded strings can be easily accessed randomly. UTF-8 is a variable-length encoding. To find the \\(i\\)-th character, we need to traverse from the beginning of the string to the \\(i\\)-th character, which requires \\(O(n)\\) time.
  • Character counting: Similar to random access, calculating the length of a UTF-16 encoded string is also an \\(O(1)\\) operation. However, calculating the length of a UTF-8 encoded string requires traversing the entire string.
  • String operations: Many string operations (such as splitting, joining, inserting, deleting, etc.) on UTF-16 encoded strings are easier to perform. Performing these operations on UTF-8 encoded strings usually requires additional calculations to ensure that invalid UTF-8 encoding is not generated.

In fact, the design of character encoding schemes for programming languages is a very interesting topic involving many factors.

  • Java's String type uses UTF-16 encoding, with each character occupying 2 bytes. This is because at the beginning of Java language design, people believed that 16 bits were sufficient to represent all possible characters. However, this was an incorrect judgment. Later, the Unicode specification expanded beyond 16 bits, so characters in Java may now be represented by a pair of 16-bit values (called \"surrogate pairs\").
  • The strings of JavaScript and TypeScript use UTF-16 encoding for reasons similar to Java. When Netscape first introduced the JavaScript language in 1995, Unicode was still in its early stages of development, and at that time, using 16-bit encoding was sufficient to represent all Unicode characters.
  • C# uses UTF-16 encoding mainly because the .NET platform was designed by Microsoft, and many of Microsoft's technologies (including the Windows operating system) extensively use UTF-16 encoding.

Due to the underestimation of character quantities by the above programming languages, they had to adopt the \"surrogate pair\" method to represent Unicode characters with lengths exceeding 16 bits. This is a reluctant compromise. On the one hand, in strings containing surrogate pairs, one character may occupy 2 bytes or 4 bytes, thus losing the advantage of fixed-length encoding. On the other hand, handling surrogate pairs requires additional code, which increases the complexity and difficulty of debugging in programming.

For the above reasons, some programming languages have proposed different encoding schemes.

  • Python's str uses Unicode encoding and adopts a flexible string representation where the stored character length depends on the largest Unicode code point in the string. If all characters in the string are ASCII characters, each character occupies 1 byte; if there are characters exceeding the ASCII range but all within the Basic Multilingual Plane (BMP), each character occupies 2 bytes; if there are characters exceeding the BMP, each character occupies 4 bytes.
  • Go language's string type uses UTF-8 encoding internally. Go language also provides the rune type, which is used to represent a single Unicode code point.
  • Rust language's str and String types use UTF-8 encoding internally. Rust also provides the char type for representing a single Unicode code point.

It should be noted that the above discussion is about how strings are stored in programming languages, which is different from how strings are stored in files or transmitted over networks. In file storage or network transmission, we usually encode strings into UTF-8 format to achieve optimal compatibility and space efficiency.

","path":["Chapter 3. Data Structures","3.4   Character Encoding *"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/","level":1,"title":"3.1   Classification of Data Structures","text":"

Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be classified from two dimensions: \"logical structure\" and \"physical structure\".

","path":["Chapter 3. Data Structures","3.1   Classification of Data Structures"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/#311-logical-structure-linear-and-non-linear","level":2,"title":"3.1.1   Logical Structure: Linear and Non-Linear","text":"

Logical structure reveals the logical relationships between data elements. In arrays and linked lists, data is arranged in a certain order, embodying the linear relationship between data; while in trees, data is arranged hierarchically from top to bottom, showing the derived relationship between \"ancestors\" and \"descendants\"; graphs are composed of nodes and edges, reflecting complex network relationships.

As shown in Figure 3-1, logical structures can be divided into two major categories: \"linear\" and \"non-linear\". Linear structures are more intuitive, indicating that data is linearly arranged in logical relationships; non-linear structures are the opposite, arranged non-linearly.

  • Linear data structures: Arrays, linked lists, stacks, queues, hash tables, where elements have a one-to-one sequential relationship.
  • Non-linear data structures: Trees, heaps, graphs, hash tables.

Non-linear data structures can be further divided into tree structures and network structures.

  • Tree structures: Trees, heaps, hash tables, where elements have a one-to-many relationship.
  • Network structures: Graphs, where elements have a many-to-many relationship.

Figure 3-1   Linear and non-linear data structures

","path":["Chapter 3. Data Structures","3.1   Classification of Data Structures"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/#312-physical-structure-contiguous-and-dispersed","level":2,"title":"3.1.2   Physical Structure: Contiguous and Dispersed","text":"

When an algorithm program runs, the data being processed is mainly stored in memory. Figure 3-2 shows a computer memory stick, where each black square contains a memory space. We can imagine memory as a huge Excel spreadsheet, where each cell can store a certain amount of data.

The system accesses data at the target location through memory addresses. As shown in Figure 3-2, the computer assigns a number to each cell in the spreadsheet according to specific rules, ensuring that each memory space has a unique memory address. With these addresses, the program can access data in memory.

Figure 3-2   Memory stick, memory space, memory address

Tip

It is worth noting that comparing memory to an Excel spreadsheet is a simplified analogy. The actual working mechanism of memory is quite complex, involving concepts such as address space, memory management, cache mechanisms, virtual memory, and physical memory.

Memory is a shared resource for all programs. When a block of memory is occupied by a program, it usually cannot be used by other programs at the same time. Therefore, in the design of data structures and algorithms, memory resources are an important consideration. For example, the peak memory occupied by an algorithm should not exceed the remaining free memory of the system; if there is a lack of contiguous large memory blocks, then the data structure chosen must be able to be stored in dispersed memory spaces.

As shown in Figure 3-3, physical structure reflects the way data is stored in computer memory, and can be divided into contiguous space storage (arrays) and dispersed space storage (linked lists). The two physical structures exhibit complementary characteristics in terms of time efficiency and space efficiency.

Figure 3-3   Contiguous space storage and dispersed space storage

It is worth noting that all data structures are implemented based on arrays, linked lists, or a combination of both. For example, stacks and queues can be implemented using either arrays or linked lists; while the implementation of hash tables may include both arrays and linked lists.

  • Can be implemented based on arrays: Stacks, queues, hash tables, trees, heaps, graphs, matrices, tensors (arrays with dimensions \\(\\geq 3\\)), etc.
  • Can be implemented based on linked lists: Stacks, queues, hash tables, trees, heaps, graphs, etc.

After initialization, linked lists can still adjust their length during program execution, so they are also called \"dynamic data structures\". After initialization, the length of arrays cannot be changed, so they are also called \"static data structures\". It is worth noting that arrays can achieve length changes by reallocating memory, thus possessing a certain degree of \"dynamism\".

Tip

If you find it difficult to understand physical structure, it is recommended to read the next chapter first, and then review this section.

","path":["Chapter 3. Data Structures","3.1   Classification of Data Structures"],"tags":[]},{"location":"chapter_data_structure/number_encoding/","level":1,"title":"3.3   Number Encoding *","text":"

Tip

In this book, chapters marked with an asterisk * are optional readings. If you are short on time or find them challenging, you may skip these initially and return to them after completing the essential chapters.

","path":["Chapter 3. Data Structures","3.3   Number Encoding *"],"tags":[]},{"location":"chapter_data_structure/number_encoding/#331-sign-magnitude-1s-complement-and-2s-complement","level":2,"title":"3.3.1   Sign-Magnitude, 1's Complement, and 2's Complement","text":"

In the table from the previous section, we found that all integer types can represent one more negative number than positive numbers. For example, the byte range is \\([-128, 127]\\). This phenomenon is counterintuitive, and its underlying reason involves knowledge of sign-magnitude, 1's complement, and 2's complement.

First, it should be noted that numbers are stored in computers in the form of \"2's complement\". Before analyzing the reasons for this, let's first define these three concepts.

  • Sign-magnitude: We treat the highest bit of the binary representation of a number as the sign bit, where \\(0\\) represents a positive number and \\(1\\) represents a negative number, and the remaining bits represent the value of the number.
  • 1's complement: The 1's complement of a positive number is the same as its sign-magnitude. For a negative number, the 1's complement is obtained by inverting all bits except the sign bit of its sign-magnitude.
  • 2's complement: The 2's complement of a positive number is the same as its sign-magnitude. For a negative number, the 2's complement is obtained by adding \\(1\\) to its 1's complement.

Figure 3-4 shows the conversion methods among sign-magnitude, 1's complement, and 2's complement.

Figure 3-4   Conversions among sign-magnitude, 1's complement, and 2's complement

Sign-magnitude, although the most intuitive, has some limitations. On one hand, the sign-magnitude of negative numbers cannot be directly used in operations. For example, calculating \\(1 + (-2)\\) in sign-magnitude yields \\(-3\\), which is clearly incorrect.

\\[ \\begin{aligned} & 1 + (-2) \\newline & \\rightarrow 0000 \\; 0001 + 1000 \\; 0010 \\newline & = 1000 \\; 0011 \\newline & \\rightarrow -3 \\end{aligned} \\]

To solve this problem, computers introduced 1's complement. If we first convert sign-magnitude to 1's complement and calculate \\(1 + (-2)\\) in 1's complement, then convert the result back to sign-magnitude, we can obtain the correct result of \\(-1\\).

\\[ \\begin{aligned} & 1 + (-2) \\newline & \\rightarrow 0000 \\; 0001 \\; \\text{(Sign-magnitude)} + 1000 \\; 0010 \\; \\text{(Sign-magnitude)} \\newline & = 0000 \\; 0001 \\; \\text{(1's complement)} + 1111 \\; 1101 \\; \\text{(1's complement)} \\newline & = 1111 \\; 1110 \\; \\text{(1's complement)} \\newline & = 1000 \\; 0001 \\; \\text{(Sign-magnitude)} \\newline & \\rightarrow -1 \\end{aligned} \\]

On the other hand, the sign-magnitude of the number zero has two representations, \\(+0\\) and \\(-0\\). This means that the number zero corresponds to two different binary encodings, which may cause ambiguity. For example, in conditional judgments, if we don't distinguish between positive zero and negative zero, it may lead to incorrect judgment results. If we want to handle the ambiguity of positive and negative zero, we need to introduce additional judgment operations, which may reduce the computational efficiency of the computer.

\\[ \\begin{aligned} +0 & \\rightarrow 0000 \\; 0000 \\newline -0 & \\rightarrow 1000 \\; 0000 \\end{aligned} \\]

Like sign-magnitude, 1's complement also has the problem of positive and negative zero ambiguity. Therefore, computers further introduced 2's complement. Let's first observe the conversion process of negative zero from sign-magnitude to 1's complement to 2's complement:

\\[ \\begin{aligned} -0 \\rightarrow \\; & 1000 \\; 0000 \\; \\text{(Sign-magnitude)} \\newline = \\; & 1111 \\; 1111 \\; \\text{(1's complement)} \\newline = 1 \\; & 0000 \\; 0000 \\; \\text{(2's complement)} \\newline \\end{aligned} \\]

Adding \\(1\\) to the 1's complement of negative zero produces a carry, but since the byte type has a length of only 8 bits, the \\(1\\) that overflows to the 9th bit is discarded. That is to say, the 2's complement of negative zero is \\(0000 \\; 0000\\), which is the same as the 2's complement of positive zero. This means that in 2's complement representation, there is only one zero, and the positive and negative zero ambiguity is thus resolved.

One last question remains: the range of the byte type is \\([-128, 127]\\), and how is the extra negative number \\(-128\\) obtained? We notice that all integers in the interval \\([-127, +127]\\) have corresponding sign-magnitude, 1's complement, and 2's complement, and sign-magnitude and 2's complement can be converted to each other.

However, the 2's complement \\(1000 \\; 0000\\) is an exception, and it does not have a corresponding sign-magnitude. According to the conversion method, we get that the sign-magnitude of this 2's complement is \\(0000 \\; 0000\\). This is clearly contradictory because this sign-magnitude represents the number \\(0\\), and its 2's complement should be itself. The computer specifies that this special 2's complement \\(1000 \\; 0000\\) represents \\(-128\\). In fact, the result of calculating \\((-1) + (-127)\\) in 2's complement is \\(-128\\).

\\[ \\begin{aligned} & (-127) + (-1) \\newline & \\rightarrow 1111 \\; 1111 \\; \\text{(Sign-magnitude)} + 1000 \\; 0001 \\; \\text{(Sign-magnitude)} \\newline & = 1000 \\; 0000 \\; \\text{(1's complement)} + 1111 \\; 1110 \\; \\text{(1's complement)} \\newline & = 1000 \\; 0001 \\; \\text{(2's complement)} + 1111 \\; 1111 \\; \\text{(2's complement)} \\newline & = 1000 \\; 0000 \\; \\text{(2's complement)} \\newline & \\rightarrow -128 \\end{aligned} \\]

You may have noticed that all the above calculations are addition operations. This hints at an important fact: the hardware circuits inside computers are mainly designed based on addition operations. This is because addition operations are simpler to implement in hardware compared to other operations (such as multiplication, division, and subtraction), easier to parallelize, and have faster operation speeds.

Please note that this does not mean that computers can only perform addition. By combining addition with some basic logical operations, computers can implement various other mathematical operations. For example, calculating the subtraction \\(a - b\\) can be converted to calculating the addition \\(a + (-b)\\); calculating multiplication and division can be converted to calculating multiple additions or subtractions.

Now we can summarize the reasons why computers use 2's complement: based on 2's complement representation, computers can use the same circuits and operations to handle the addition of positive and negative numbers, without the need to design special hardware circuits to handle subtraction, and without the need to specially handle the ambiguity problem of positive and negative zero. This greatly simplifies hardware design and improves operational efficiency.

The design of 2's complement is very ingenious. Due to space limitations, we will stop here. Interested readers are encouraged to explore further.

","path":["Chapter 3. Data Structures","3.3   Number Encoding *"],"tags":[]},{"location":"chapter_data_structure/number_encoding/#332-floating-point-number-encoding","level":2,"title":"3.3.2   Floating-Point Number Encoding","text":"

Careful readers may have noticed: int and float have the same length, both are 4 bytes, but why does float have a much larger range than int? This is very counterintuitive because it stands to reason that float needs to represent decimals, so the range should be smaller.

In fact, this is because floating-point number float uses a different representation method. Let's denote a 32-bit binary number as:

\\[ b_{31} b_{30} b_{29} \\ldots b_2 b_1 b_0 \\]

According to the IEEE 754 standard, a 32-bit float consists of the following three parts.

  • Sign bit \\(\\mathrm{S}\\): occupies 1 bit, corresponding to \\(b_{31}\\).
  • Exponent bit \\(\\mathrm{E}\\): occupies 8 bits, corresponding to \\(b_{30} b_{29} \\ldots b_{23}\\).
  • Fraction bit \\(\\mathrm{N}\\): occupies 23 bits, corresponding to \\(b_{22} b_{21} \\ldots b_0\\).

The calculation method for the value corresponding to the binary float is:

\\[ \\text {val} = (-1)^{b_{31}} \\times 2^{\\left(b_{30} b_{29} \\ldots b_{23}\\right)_2-127} \\times\\left(1 . b_{22} b_{21} \\ldots b_0\\right)_2 \\]

Converted to decimal, the calculation formula is:

\\[ \\text {val}=(-1)^{\\mathrm{S}} \\times 2^{\\mathrm{E} -127} \\times (1 + \\mathrm{N}) \\]

The range of each component is:

\\[ \\begin{aligned} \\mathrm{S} \\in & \\{ 0, 1\\}, \\quad \\mathrm{E} \\in \\{ 1, 2, \\dots, 254 \\} \\newline (1 + \\mathrm{N}) = & (1 + \\sum_{i=1}^{23} b_{23-i} 2^{-i}) \\subset [1, 2 - 2^{-23}] \\end{aligned} \\]

Figure 3-5   Calculation example of float under IEEE 754 standard

Observing Figure 3-5, given example data \\(\\mathrm{S} = 0\\), \\(\\mathrm{E} = 124\\), \\(\\mathrm{N} = 2^{-2} + 2^{-3} = 0.375\\), we have:

\\[ \\text { val } = (-1)^0 \\times 2^{124 - 127} \\times (1 + 0.375) = 0.171875 \\]

Now we can answer the initial question: the representation of float includes an exponent bit, resulting in a range far greater than int. According to the above calculation, the maximum positive number that float can represent is \\(2^{254 - 127} \\times (2 - 2^{-23}) \\approx 3.4 \\times 10^{38}\\), and the minimum negative number can be obtained by switching the sign bit.

Although floating-point number float expands the range, its side effect is sacrificing precision. The integer type int uses all 32 bits to represent numbers, and the numbers are evenly distributed; however, due to the existence of the exponent bit, the larger the value of floating-point number float, the larger the difference between two adjacent numbers tends to be.

As shown in Table 3-2, exponent bits \\(\\mathrm{E} = 0\\) and \\(\\mathrm{E} = 255\\) have special meanings, used to represent zero, infinity, \\(\\mathrm{NaN}\\), etc.

Table 3-2   Meaning of exponent bits

Exponent Bit E Fraction Bit \\(\\mathrm{N} = 0\\) Fraction Bit \\(\\mathrm{N} \\ne 0\\) Calculation Formula \\(0\\) \\(\\pm 0\\) Subnormal Number \\((-1)^{\\mathrm{S}} \\times 2^{-126} \\times (0.\\mathrm{N})\\) \\(1, 2, \\dots, 254\\) Normal Number Normal Number \\((-1)^{\\mathrm{S}} \\times 2^{(\\mathrm{E} -127)} \\times (1.\\mathrm{N})\\) \\(255\\) \\(\\pm \\infty\\) \\(\\mathrm{NaN}\\)

It is worth noting that subnormal numbers significantly improve the precision of floating-point numbers. The smallest positive normal number is \\(2^{-126}\\), and the smallest positive subnormal number is \\(2^{-126} \\times 2^{-23}\\).

Double-precision double also uses a representation method similar to float, which will not be elaborated here.

","path":["Chapter 3. Data Structures","3.3   Number Encoding *"],"tags":[]},{"location":"chapter_data_structure/summary/","level":1,"title":"3.5   Summary","text":"","path":["Chapter 3. Data Structures","3.5   Summary"],"tags":[]},{"location":"chapter_data_structure/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Data structures can be classified from two perspectives: logical structure and physical structure. Logical structure describes the logical relationships between data elements, while physical structure describes how data is stored in computer memory.
  • Common logical structures include linear, tree, and network structures. We typically classify data structures as linear (arrays, linked lists, stacks, queues) and non-linear (trees, graphs, heaps) based on their logical structure. The implementation of hash tables may involve both linear and non-linear data structures.
  • When a program runs, data is stored in computer memory. Each memory space has a corresponding memory address, and the program accesses data through these memory addresses.
  • Physical structures are primarily divided into contiguous space storage (arrays) and dispersed space storage (linked lists). All data structures are implemented using arrays, linked lists, or a combination of both.
  • Basic data types in computers include integers byte, short, int, long, floating-point numbers float, double, characters char, and booleans bool. Their value ranges depend on the size of space they occupy and their representation method.
  • Sign-magnitude, 1's complement, and 2's complement are three methods for encoding numbers in computers, and they can be converted into each other. The most significant bit of sign-magnitude is the sign bit, and the remaining bits represent the value of the number.
  • Integers are stored in computers in 2's complement form. Under 2's complement representation, computers can treat the addition of positive and negative numbers uniformly, without needing to design special hardware circuits for subtraction, and there is no ambiguity of positive and negative zero.
  • The encoding of floating-point numbers consists of 1 sign bit, 8 exponent bits, and 23 fraction bits. Due to the exponent bits, the range of floating-point numbers is much larger than that of integers, at the cost of sacrificing precision.
  • ASCII is the earliest English character set, with a length of 1 byte, containing a total of 127 characters. GBK is a commonly used Chinese character set, containing over 20,000 Chinese characters. Unicode is committed to providing a complete character set standard, collecting characters from various languages around the world, thereby solving the garbled text problem caused by inconsistent character encoding methods.
  • UTF-8 is the most popular Unicode encoding method, with excellent universality. It is a variable-length encoding method with good scalability, effectively improving storage space efficiency. UTF-16 and UTF-32 are fixed-length encoding methods. When encoding Chinese characters, UTF-16 occupies less space than UTF-8. Programming languages such as Java and C# use UTF-16 encoding by default.
","path":["Chapter 3. Data Structures","3.5   Summary"],"tags":[]},{"location":"chapter_data_structure/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Why do hash tables contain both linear and non-linear data structures?

The underlying structure of a hash table is an array. To resolve hash collisions, we may use \"chaining\" (discussed in the subsequent \"Hash Collision\" section): each bucket in the array points to a linked list, which may be converted to a tree (usually a red-black tree) when the list length exceeds a certain threshold.

From a storage perspective, the underlying structure of a hash table is an array, where each bucket slot may contain a value, a linked list, or a tree. Therefore, hash tables may contain both linear data structures (arrays, linked lists) and non-linear data structures (trees).

Q: Is the length of the char type 1 byte?

The length of the char type is determined by the encoding method used by the programming language. For example, Java, JavaScript, TypeScript, and C# all use UTF-16 encoding (to store Unicode code points), so the char type has a length of 2 bytes.

Q: Is there ambiguity in referring to array-based data structures as \"static data structures\"? Stacks can also perform \"dynamic\" operations such as push and pop.

Stacks can indeed implement dynamic data operations, but the data structure is still \"static\" (fixed length). Although array-based data structures can dynamically add or remove elements, their capacity is fixed. If the data volume exceeds the pre-allocated size, a new larger array needs to be created, and the contents of the old array must be copied to the new array.

Q: When constructing a stack (queue), its size is not specified. Why are they \"static data structures\"?

In high-level programming languages, we do not need to manually specify the initial capacity of a stack (queue); this work is automatically completed within the class. For example, the initial capacity of Java's ArrayList is typically 10. Additionally, the expansion operation is also automatically implemented. See the subsequent \"List\" section for details.

Q: The method of converting sign-magnitude to 2's complement is \"first negate then add 1\". So converting 2's complement to sign-magnitude should be the inverse operation \"first subtract 1 then negate\". However, 2's complement can also be converted to sign-magnitude through \"first negate then add 1\". Why is this?

This is because the mutual conversion between sign-magnitude and 2's complement is actually the process of computing the \"complement\". Let us first define the complement: assuming \\(a + b = c\\), then we say that \\(a\\) is the complement of \\(b\\) to \\(c\\), and conversely, \\(b\\) is the complement of \\(a\\) to \\(c\\).

Given an \\(n = 4\\) bit binary number \\(0010\\), if we treat this number as sign-magnitude (ignoring the sign bit), then its 2's complement can be obtained through \"first negate then add 1\":

\\[ 0010 \\rightarrow 1101 \\rightarrow 1110 \\]

We find that the sum of sign-magnitude and 2's complement is \\(0010 + 1110 = 10000\\), which means the 2's complement \\(1110\\) is the \"complement\" of sign-magnitude \\(0010\\) to \\(10000\\). This means the above \"first negate then add 1\" is actually the process of computing the complement to \\(10000\\).

So, what is the \"complement\" of 2's complement \\(1110\\) to \\(10000\\)? We can still use \"first negate then add 1\" to obtain it:

\\[ 1110 \\rightarrow 0001 \\rightarrow 0010 \\]

In other words, sign-magnitude and 2's complement are each other's \"complement\" to \\(10000\\), so \"sign-magnitude to 2's complement\" and \"2's complement to sign-magnitude\" can be implemented using the same operation (first negate then add 1).

Of course, we can also use the inverse operation to find the sign-magnitude of 2's complement \\(1110\\), that is, \"first subtract 1 then negate\":

\\[ 1110 \\rightarrow 1101 \\rightarrow 0010 \\]

In summary, both \"first negate then add 1\" and \"first subtract 1 then negate\" are computing the complement to \\(10000\\), and they are equivalent.

Essentially, the \"negate\" operation is actually finding the complement to \\(1111\\) (because sign-magnitude + 1's complement = 1111 always holds); and adding 1 to the 1's complement yields the 2's complement, which is the complement to \\(10000\\).

The above uses \\(n = 4\\) as an example, and it can be generalized to binary numbers of any number of bits.

","path":["Chapter 3. Data Structures","3.5   Summary"],"tags":[]},{"location":"chapter_divide_and_conquer/","level":1,"title":"Chapter 12.   Divide and Conquer","text":"

Abstract

Difficult problems are decomposed layer by layer, with each decomposition making them simpler.

Divide and conquer reveals an important truth: start with simplicity, and nothing remains complex.

","path":["Chapter 12. Divide and Conquer","Chapter 12.   Divide and Conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 12.1   Divide and Conquer Algorithms
  • 12.2   Divide and Conquer Search Strategy
  • 12.3   Building a Binary Tree Problem
  • 12.4   Hanoi Tower Problem
  • 12.5   Summary
","path":["Chapter 12. Divide and Conquer","Chapter 12.   Divide and Conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/binary_search_recur/","level":1,"title":"12.2   Divide and Conquer Search Strategy","text":"

We have already learned that search algorithms are divided into two major categories.

  • Brute-force search: Implemented by traversing the data structure, with a time complexity of \\(O(n)\\).
  • Adaptive search: Utilizes unique data organization forms or prior information, with time complexity reaching \\(O(\\log n)\\) or even \\(O(1)\\).

In fact, search algorithms with time complexity of \\(O(\\log n)\\) are typically implemented based on the divide and conquer strategy, such as binary search and trees.

  • Each step of binary search divides the problem (searching for a target element in an array) into a smaller problem (searching for the target element in half of the array), continuing until the array is empty or the target element is found.
  • Trees are representative of the divide and conquer idea. In data structures such as binary search trees, AVL trees, and heaps, the time complexity of various operations is \\(O(\\log n)\\).

The divide and conquer strategy of binary search is as follows.

  • The problem can be decomposed: Binary search recursively decomposes the original problem (searching in an array) into subproblems (searching in half of the array), achieved by comparing the middle element with the target element.
  • Subproblems are independent: In binary search, each round only processes one subproblem, which is not affected by other subproblems.
  • Solutions of subproblems do not need to be merged: Binary search aims to find a specific element, so there is no need to merge the solutions of subproblems. When a subproblem is solved, the original problem is also solved.

Divide and conquer can improve search efficiency because brute-force search can only eliminate one option per round, while divide and conquer search can eliminate half of the options per round.

","path":["Chapter 12. Divide and Conquer","12.2   Divide and Conquer Search Strategy"],"tags":[]},{"location":"chapter_divide_and_conquer/binary_search_recur/#1-implementing-binary-search-based-on-divide-and-conquer","level":3,"title":"1.   Implementing Binary Search Based on Divide and Conquer","text":"

In previous sections, binary search was implemented based on iteration. Now we implement it based on divide and conquer (recursion).

Question

Given a sorted array nums of length \\(n\\), where all elements are unique, find the element target.

From a divide and conquer perspective, we denote the subproblem corresponding to the search interval \\([i, j]\\) as \\(f(i, j)\\).

Starting from the original problem \\(f(0, n-1)\\), perform binary search through the following steps.

  1. Calculate the midpoint \\(m\\) of the search interval \\([i, j]\\), and use it to eliminate half of the search interval.
  2. Recursively solve the subproblem reduced by half in size, which could be \\(f(i, m-1)\\) or \\(f(m+1, j)\\).
  3. Repeat steps 1. and 2. until target is found or the interval is empty and return.

Figure 12-4 shows the divide and conquer process of binary search for element \\(6\\) in an array.

Figure 12-4   Divide and conquer process of binary search

In the implementation code, we declare a recursive function dfs() to solve the problem \\(f(i, j)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_recur.py
def dfs(nums: list[int], target: int, i: int, j: int) -> int:\n    \"\"\"Binary search: problem f(i, j)\"\"\"\n    # If the interval is empty, it means there is no target element, return -1\n    if i > j:\n        return -1\n    # Calculate the midpoint index m\n    m = (i + j) // 2\n    if nums[m] < target:\n        # Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j)\n    elif nums[m] > target:\n        # Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1)\n    else:\n        # Found the target element, return its index\n        return m\n\ndef binary_search(nums: list[int], target: int) -> int:\n    \"\"\"Binary search\"\"\"\n    n = len(nums)\n    # Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1)\n
binary_search_recur.cpp
/* Binary search: problem f(i, j) */\nint dfs(vector<int> &nums, int target, int i, int j) {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(vector<int> &nums, int target) {\n    int n = nums.size();\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.java
/* Binary search: problem f(i, j) */\nint dfs(int[] nums, int target, int i, int j) {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(int[] nums, int target) {\n    int n = nums.length;\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.cs
/* Binary search: problem f(i, j) */\nint DFS(int[] nums, int target, int i, int j) {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return DFS(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return DFS(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint BinarySearch(int[] nums, int target) {\n    int n = nums.Length;\n    // Solve the problem f(0, n-1)\n    return DFS(nums, target, 0, n - 1);\n}\n
binary_search_recur.go
/* Binary search: problem f(i, j) */\nfunc dfs(nums []int, target, i, j int) int {\n    // If interval is empty, indicating no target element, return -1\n    if i > j {\n        return -1\n    }\n    // Calculate midpoint index\n    m := i + ((j - i) >> 1)\n    // Compare midpoint with target element\n    if nums[m] < target {\n        // If smaller, recurse on right half of array\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m+1, j)\n    } else if nums[m] > target {\n        // If larger, recurse on left half of array\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m-1)\n    } else {\n        // Found the target element, return its index\n        return m\n    }\n}\n\n/* Binary search */\nfunc binarySearch(nums []int, target int) int {\n    n := len(nums)\n    return dfs(nums, target, 0, n-1)\n}\n
binary_search_recur.swift
/* Binary search: problem f(i, j) */\nfunc dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int {\n    // If the interval is empty, it means there is no target element, return -1\n    if i > j {\n        return -1\n    }\n    // Calculate the midpoint index m\n    let m = (i + j) / 2\n    if nums[m] < target {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums: nums, target: target, i: m + 1, j: j)\n    } else if nums[m] > target {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums: nums, target: target, i: i, j: m - 1)\n    } else {\n        // Found the target element, return its index\n        return m\n    }\n}\n\n/* Binary search */\nfunc binarySearch(nums: [Int], target: Int) -> Int {\n    // Solve the problem f(0, n-1)\n    dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1)\n}\n
binary_search_recur.js
/* Binary search: problem f(i, j) */\nfunction dfs(nums, target, i, j) {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    const m = i + ((j - i) >> 1);\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfunction binarySearch(nums, target) {\n    const n = nums.length;\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.ts
/* Binary search: problem f(i, j) */\nfunction dfs(nums: number[], target: number, i: number, j: number): number {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    const m = i + ((j - i) >> 1);\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfunction binarySearch(nums: number[], target: number): number {\n    const n = nums.length;\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.dart
/* Binary search: problem f(i, j) */\nint dfs(List<int> nums, int target, int i, int j) {\n  // If the interval is empty, it means there is no target element, return -1\n  if (i > j) {\n    return -1;\n  }\n  // Calculate the midpoint index m\n  int m = (i + j) ~/ 2;\n  if (nums[m] < target) {\n    // Recursion subproblem f(m+1, j)\n    return dfs(nums, target, m + 1, j);\n  } else if (nums[m] > target) {\n    // Recursion subproblem f(i, m-1)\n    return dfs(nums, target, i, m - 1);\n  } else {\n    // Found the target element, return its index\n    return m;\n  }\n}\n\n/* Binary search */\nint binarySearch(List<int> nums, int target) {\n  int n = nums.length;\n  // Solve the problem f(0, n-1)\n  return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.rs
/* Binary search: problem f(i, j) */\nfn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 {\n    // If the interval is empty, it means there is no target element, return -1\n    if i > j {\n        return -1;\n    }\n    let m: i32 = i + (j - i) / 2;\n    if nums[m as usize] < target {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if nums[m as usize] > target {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfn binary_search(nums: &[i32], target: i32) -> i32 {\n    let n = nums.len() as i32;\n    // Solve the problem f(0, n-1)\n    dfs(nums, target, 0, n - 1)\n}\n
binary_search_recur.c
/* Binary search: problem f(i, j) */\nint dfs(int nums[], int target, int i, int j) {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // Found the target element, return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(int nums[], int target, int numsSize) {\n    int n = numsSize;\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
binary_search_recur.kt
/* Binary search: problem f(i, j) */\nfun dfs(\n    nums: IntArray,\n    target: Int,\n    i: Int,\n    j: Int\n): Int {\n    // If the interval is empty, it means there is no target element, return -1\n    if (i > j) {\n        return -1\n    }\n    // Calculate the midpoint index m\n    val m = (i + j) / 2\n    return if (nums[m] < target) {\n        // Recursion subproblem f(m+1, j)\n        dfs(nums, target, m + 1, j)\n    } else if (nums[m] > target) {\n        // Recursion subproblem f(i, m-1)\n        dfs(nums, target, i, m - 1)\n    } else {\n        // Found the target element, return its index\n        m\n    }\n}\n\n/* Binary search */\nfun binarySearch(nums: IntArray, target: Int): Int {\n    val n = nums.size\n    // Solve the problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1)\n}\n
binary_search_recur.rb
### Binary search: problem f(i, j) ###\ndef dfs(nums, target, i, j)\n  # If the interval is empty, it means there is no target element, return -1\n  return -1 if i > j\n\n  # Calculate the midpoint index m\n  m = (i + j) / 2\n\n  if nums[m] < target\n    # Recursion subproblem f(m+1, j)\n    return dfs(nums, target, m + 1, j)\n  elsif nums[m] > target\n    # Recursion subproblem f(i, m-1)\n    return dfs(nums, target, i, m - 1)\n  else\n    # Found the target element, return its index\n    return m\n  end\nend\n\n### Binary search ###\ndef binary_search(nums, target)\n  n = nums.length\n  # Solve the problem f(0, n-1)\n  dfs(nums, target, 0, n - 1)\nend\n
","path":["Chapter 12. Divide and Conquer","12.2   Divide and Conquer Search Strategy"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/","level":1,"title":"12.3   Building a Binary Tree Problem","text":"

Question

Given the preorder traversal preorder and inorder traversal inorder of a binary tree, construct the binary tree and return the root node of the binary tree. Assume there are no duplicate node values in the binary tree (as shown in Figure 12-5).

Figure 12-5   Example data for building a binary tree

","path":["Chapter 12. Divide and Conquer","12.3   Building a Binary Tree Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#1-determining-if-it-is-a-divide-and-conquer-problem","level":3,"title":"1.   Determining If It Is a Divide and Conquer Problem","text":"

The original problem is defined as constructing a binary tree from preorder and inorder, which is a typical divide and conquer problem.

  • The problem can be decomposed: From a divide and conquer perspective, we can divide the original problem into two subproblems: constructing the left subtree and constructing the right subtree, plus one operation: initializing the root node. For each subtree (subproblem), we can still reuse the above division method, dividing it into smaller subtrees (subproblems) until the smallest subproblem (empty subtree) is reached.
  • Subproblems are independent: The left and right subtrees are independent of each other; there is no overlap between them. When constructing the left subtree, we only need to focus on the parts of the inorder and preorder traversals corresponding to the left subtree. The same applies to the right subtree.
  • Solutions of subproblems can be merged: Once we have the left and right subtrees (solutions of subproblems), we can link them to the root node to obtain the solution to the original problem.
","path":["Chapter 12. Divide and Conquer","12.3   Building a Binary Tree Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#2-how-to-divide-subtrees","level":3,"title":"2.   How to Divide Subtrees","text":"

Based on the above analysis, this problem can be solved using divide and conquer, but how do we divide the left and right subtrees through the preorder traversal preorder and inorder traversal inorder?

According to the definition, both preorder and inorder can be divided into three parts.

  • Preorder traversal: [ Root Node | Left Subtree | Right Subtree ], for example, the tree in Figure 12-5 corresponds to [ 3 | 9 | 2 1 7 ].
  • Inorder traversal: [ Left Subtree | Root Node | Right Subtree ], for example, the tree in Figure 12-5 corresponds to [ 9 | 3 | 1 2 7 ].

Using the data from the figure above as an example, we can obtain the division results through the steps shown in Figure 12-6.

  1. The first element 3 in the preorder traversal is the value of the root node.
  2. Find the index of root node 3 in inorder, and use this index to divide inorder into [ 9 | 3 | 1 2 7 ].
  3. Based on the division result of inorder, it is easy to determine that the left and right subtrees have 1 and 3 nodes respectively, allowing us to divide preorder into [ 3 | 9 | 2 1 7 ].

Figure 12-6   Dividing subtrees in preorder and inorder traversals

","path":["Chapter 12. Divide and Conquer","12.3   Building a Binary Tree Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#3-describing-subtree-intervals-based-on-variables","level":3,"title":"3.   Describing Subtree Intervals Based on Variables","text":"

Based on the above division method, we have obtained the index intervals of the root node, left subtree, and right subtree in preorder and inorder. To describe these index intervals, we need to use several pointer variables.

  • Denote the index of the current tree's root node in preorder as \\(i\\).
  • Denote the index of the current tree's root node in inorder as \\(m\\).
  • Denote the index interval of the current tree in inorder as \\([l, r]\\).

As shown in Table 12-1, through these variables we can represent the index of the root node in preorder and the index intervals of the subtrees in inorder.

Table 12-1   Indices of root node and subtrees in preorder and inorder traversals

Root node index in preorder Subtree index interval in inorder Current tree \\(i\\) \\([l, r]\\) Left subtree \\(i + 1\\) \\([l, m-1]\\) Right subtree \\(i + 1 + (m - l)\\) \\([m+1, r]\\)

Please note that \\((m-l)\\) in the right subtree root node index means \"the number of nodes in the left subtree\". It is recommended to understand this in conjunction with Figure 12-7.

Figure 12-7   Index interval representation of root node and left and right subtrees

","path":["Chapter 12. Divide and Conquer","12.3   Building a Binary Tree Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#4-code-implementation","level":3,"title":"4.   Code Implementation","text":"

To improve the efficiency of querying \\(m\\), we use a hash table hmap to store the mapping from elements in the inorder array to their indices:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby build_tree.py
def dfs(\n    preorder: list[int],\n    inorder_map: dict[int, int],\n    i: int,\n    l: int,\n    r: int,\n) -> TreeNode | None:\n    \"\"\"Build binary tree: divide and conquer\"\"\"\n    # Terminate when the subtree interval is empty\n    if r - l < 0:\n        return None\n    # Initialize the root node\n    root = TreeNode(preorder[i])\n    # Query m to divide the left and right subtrees\n    m = inorder_map[preorder[i]]\n    # Subproblem: build the left subtree\n    root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)\n    # Subproblem: build the right subtree\n    root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)\n    # Return the root node\n    return root\n\ndef build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None:\n    \"\"\"Build binary tree\"\"\"\n    # Initialize hash map, storing the mapping from inorder elements to indices\n    inorder_map = {val: i for i, val in enumerate(inorder)}\n    root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1)\n    return root\n
build_tree.cpp
/* Build binary tree: divide and conquer */\nTreeNode *dfs(vector<int> &preorder, unordered_map<int, int> &inorderMap, int i, int l, int r) {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0)\n        return NULL;\n    // Initialize the root node\n    TreeNode *root = new TreeNode(preorder[i]);\n    // Query m to divide the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root->left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nTreeNode *buildTree(vector<int> &preorder, vector<int> &inorder) {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    unordered_map<int, int> inorderMap;\n    for (int i = 0; i < inorder.size(); i++) {\n        inorderMap[inorder[i]] = i;\n    }\n    TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1);\n    return root;\n}\n
build_tree.java
/* Build binary tree: divide and conquer */\nTreeNode dfs(int[] preorder, Map<Integer, Integer> inorderMap, int i, int l, int r) {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0)\n        return null;\n    // Initialize the root node\n    TreeNode root = new TreeNode(preorder[i]);\n    // Query m to divide the left and right subtrees\n    int m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nTreeNode buildTree(int[] preorder, int[] inorder) {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    Map<Integer, Integer> inorderMap = new HashMap<>();\n    for (int i = 0; i < inorder.length; i++) {\n        inorderMap.put(inorder[i], i);\n    }\n    TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
build_tree.cs
/* Build binary tree: divide and conquer */\nTreeNode? DFS(int[] preorder, Dictionary<int, int> inorderMap, int i, int l, int r) {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0)\n        return null;\n    // Initialize the root node\n    TreeNode root = new(preorder[i]);\n    // Query m to divide the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root.left = DFS(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nTreeNode? BuildTree(int[] preorder, int[] inorder) {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    Dictionary<int, int> inorderMap = [];\n    for (int i = 0; i < inorder.Length; i++) {\n        inorderMap.TryAdd(inorder[i], i);\n    }\n    TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1);\n    return root;\n}\n
build_tree.go
/* Build binary tree: divide and conquer */\nfunc dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode {\n    // Terminate when the subtree interval is empty\n    if r-l < 0 {\n        return nil\n    }\n    // Initialize the root node\n    root := NewTreeNode(preorder[i])\n    // Query m to divide the left and right subtrees\n    m := inorderMap[preorder[i]]\n    // Subproblem: build the left subtree\n    root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1)\n    // Subproblem: build the right subtree\n    root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r)\n    // Return the root node\n    return root\n}\n\n/* Build binary tree */\nfunc buildTree(preorder, inorder []int) *TreeNode {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    inorderMap := make(map[int]int, len(inorder))\n    for i := 0; i < len(inorder); i++ {\n        inorderMap[inorder[i]] = i\n    }\n\n    root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1)\n    return root\n}\n
build_tree.swift
/* Build binary tree: divide and conquer */\nfunc dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? {\n    // Terminate when the subtree interval is empty\n    if r - l < 0 {\n        return nil\n    }\n    // Initialize the root node\n    let root = TreeNode(x: preorder[i])\n    // Query m to divide the left and right subtrees\n    let m = inorderMap[preorder[i]]!\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1)\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r)\n    // Return the root node\n    return root\n}\n\n/* Build binary tree */\nfunc buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }\n    return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1)\n}\n
build_tree.js
/* Build binary tree: divide and conquer */\nfunction dfs(preorder, inorderMap, i, l, r) {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0) return null;\n    // Initialize the root node\n    const root = new TreeNode(preorder[i]);\n    // Query m to divide the left and right subtrees\n    const m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nfunction buildTree(preorder, inorder) {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    let inorderMap = new Map();\n    for (let i = 0; i < inorder.length; i++) {\n        inorderMap.set(inorder[i], i);\n    }\n    const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
build_tree.ts
/* Build binary tree: divide and conquer */\nfunction dfs(\n    preorder: number[],\n    inorderMap: Map<number, number>,\n    i: number,\n    l: number,\n    r: number\n): TreeNode | null {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0) return null;\n    // Initialize the root node\n    const root: TreeNode = new TreeNode(preorder[i]);\n    // Query m to divide the left and right subtrees\n    const m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nfunction buildTree(preorder: number[], inorder: number[]): TreeNode | null {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    let inorderMap = new Map<number, number>();\n    for (let i = 0; i < inorder.length; i++) {\n        inorderMap.set(inorder[i], i);\n    }\n    const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
build_tree.dart
/* Build binary tree: divide and conquer */\nTreeNode? dfs(\n  List<int> preorder,\n  Map<int, int> inorderMap,\n  int i,\n  int l,\n  int r,\n) {\n  // Terminate when the subtree interval is empty\n  if (r - l < 0) {\n    return null;\n  }\n  // Initialize the root node\n  TreeNode? root = TreeNode(preorder[i]);\n  // Query m to divide the left and right subtrees\n  int m = inorderMap[preorder[i]]!;\n  // Subproblem: build the left subtree\n  root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n  // Subproblem: build the right subtree\n  root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n  // Return the root node\n  return root;\n}\n\n/* Build binary tree */\nTreeNode? buildTree(List<int> preorder, List<int> inorder) {\n  // Initialize hash map, storing the mapping from inorder elements to indices\n  Map<int, int> inorderMap = {};\n  for (int i = 0; i < inorder.length; i++) {\n    inorderMap[inorder[i]] = i;\n  }\n  TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n  return root;\n}\n
build_tree.rs
/* Build binary tree: divide and conquer */\nfn dfs(\n    preorder: &[i32],\n    inorder_map: &HashMap<i32, i32>,\n    i: i32,\n    l: i32,\n    r: i32,\n) -> Option<Rc<RefCell<TreeNode>>> {\n    // Terminate when the subtree interval is empty\n    if r - l < 0 {\n        return None;\n    }\n    // Initialize the root node\n    let root = TreeNode::new(preorder[i as usize]);\n    // Query m to divide the left and right subtrees\n    let m = inorder_map.get(&preorder[i as usize]).unwrap();\n    // Subproblem: build the left subtree\n    root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    Some(root)\n}\n\n/* Build binary tree */\nfn build_tree(preorder: &[i32], inorder: &[i32]) -> Option<Rc<RefCell<TreeNode>>> {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    let mut inorder_map: HashMap<i32, i32> = HashMap::new();\n    for i in 0..inorder.len() {\n        inorder_map.insert(inorder[i], i as i32);\n    }\n    let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1);\n    root\n}\n
build_tree.c
/* Build binary tree: divide and conquer */\nTreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0)\n        return NULL;\n    // Initialize the root node\n    TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode));\n    root->val = preorder[i];\n    root->left = NULL;\n    root->right = NULL;\n    // Query m to divide the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size);\n    // Subproblem: build the right subtree\n    root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size);\n    // Return the root node\n    return root;\n}\n\n/* Build binary tree */\nTreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE);\n    for (int i = 0; i < inorderSize; i++) {\n        inorderMap[inorder[i]] = i;\n    }\n    TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize);\n    free(inorderMap);\n    return root;\n}\n
build_tree.kt
/* Build binary tree: divide and conquer */\nfun dfs(\n    preorder: IntArray,\n    inorderMap: Map<Int?, Int?>,\n    i: Int,\n    l: Int,\n    r: Int\n): TreeNode? {\n    // Terminate when the subtree interval is empty\n    if (r - l < 0) return null\n    // Initialize the root node\n    val root = TreeNode(preorder[i])\n    // Query m to divide the left and right subtrees\n    val m = inorderMap[preorder[i]]!!\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1)\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r)\n    // Return the root node\n    return root\n}\n\n/* Build binary tree */\nfun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? {\n    // Initialize hash map, storing the mapping from inorder elements to indices\n    val inorderMap = HashMap<Int?, Int?>()\n    for (i in inorder.indices) {\n        inorderMap[inorder[i]] = i\n    }\n    val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1)\n    return root\n}\n
build_tree.rb
### Build binary tree: divide and conquer ###\ndef dfs(preorder, inorder_map, i, l, r)\n  # Terminate when the subtree interval is empty\n  return if r - l < 0\n\n  # Initialize the root node\n  root = TreeNode.new(preorder[i])\n  # Query m to divide the left and right subtrees\n  m = inorder_map[preorder[i]]\n  # Subproblem: build the left subtree\n  root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)\n  # Subproblem: build the right subtree\n  root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)\n\n  # Return the root node\n  root\nend\n\n### Build binary tree ###\ndef build_tree(preorder, inorder)\n  # Initialize hash map, storing the mapping from inorder elements to indices\n  inorder_map = {}\n  inorder.each_with_index { |val, i| inorder_map[val] = i }\n  dfs(preorder, inorder_map, 0, 0, inorder.length - 1)\nend\n

Figure 12-8 shows the recursive process of building the binary tree. Each node is established during the downward \"recursion\" process, while each edge (reference) is established during the upward \"return\" process.

<1><2><3><4><5><6><7><8><9>

Figure 12-8   Recursive process of building a binary tree

The division results of the preorder traversal preorder and inorder traversal inorder within each recursive function are shown in Figure 12-9.

Figure 12-9   Division results in each recursive function

Let the number of nodes in the tree be \\(n\\). Initializing each node (executing one recursive function dfs()) takes \\(O(1)\\) time. Therefore, the overall time complexity is \\(O(n)\\).

The hash table stores the mapping from inorder elements to their indices, with a space complexity of \\(O(n)\\). In the worst case, when the binary tree degenerates into a linked list, the recursion depth reaches \\(n\\), using \\(O(n)\\) stack frame space. Therefore, the overall space complexity is \\(O(n)\\).

","path":["Chapter 12. Divide and Conquer","12.3   Building a Binary Tree Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/","level":1,"title":"12.1   Divide and Conquer Algorithms","text":"

Divide and conquer is a very important and common algorithm strategy. Divide and conquer is typically implemented based on recursion, consisting of two steps: \"divide\" and \"conquer\".

  1. Divide (partition phase): Recursively divide the original problem into two or more subproblems until the smallest subproblem is reached.
  2. Conquer (merge phase): Starting from the smallest subproblems with known solutions, merge the solutions of subproblems from bottom to top to construct the solution to the original problem.

As shown in Figure 12-1, \"merge sort\" is one of the typical applications of the divide and conquer strategy.

  1. Divide: Recursively divide the original array (original problem) into two subarrays (subproblems) until the subarray has only one element (smallest subproblem).
  2. Conquer: Merge the sorted subarrays (solutions to subproblems) from bottom to top to obtain a sorted original array (solution to the original problem).

Figure 12-1   Divide and conquer strategy of merge sort

","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1211-how-to-determine-divide-and-conquer-problems","level":2,"title":"12.1.1   How to Determine Divide and Conquer Problems","text":"

Whether a problem is suitable for solving with divide and conquer can usually be determined based on the following criteria.

  1. The problem can be decomposed: The original problem can be divided into smaller, similar subproblems, and can be recursively divided in the same way.
  2. Subproblems are independent: There is no overlap between subproblems, they are independent of each other and can be solved independently.
  3. Solutions of subproblems can be merged: The solution to the original problem is obtained by merging the solutions of subproblems.

Clearly, merge sort satisfies these three criteria.

  1. The problem can be decomposed: Recursively divide the array (original problem) into two subarrays (subproblems).
  2. Subproblems are independent: Each subarray can be sorted independently (subproblems can be solved independently).
  3. Solutions of subproblems can be merged: Two sorted subarrays (solutions of subproblems) can be merged into one sorted array (solution of the original problem).
","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1212-improving-efficiency-through-divide-and-conquer","level":2,"title":"12.1.2   Improving Efficiency Through Divide and Conquer","text":"

Divide and conquer can not only effectively solve algorithmic problems but often also improve algorithm efficiency. In sorting algorithms, quick sort, merge sort, and heap sort are faster than selection, bubble, and insertion sort because they apply the divide and conquer strategy.

This raises the question: Why can divide and conquer improve algorithm efficiency, and what is the underlying logic? In other words, why is dividing a large problem into multiple subproblems, solving the subproblems, and merging their solutions more efficient than directly solving the original problem? This question can be discussed from two aspects: operation count and parallel computation.

","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1-operation-count-optimization","level":3,"title":"1.   Operation Count Optimization","text":"

Taking \"bubble sort\" as an example, processing an array of length \\(n\\) requires \\(O(n^2)\\) time. Suppose we divide the array into two subarrays from the midpoint as shown in Figure 12-2, the division requires \\(O(n)\\) time, sorting each subarray requires \\(O((n / 2)^2)\\) time, and merging the two subarrays requires \\(O(n)\\) time, resulting in an overall time complexity of:

\\[ O(n + (\\frac{n}{2})^2 \\times 2 + n) = O(\\frac{n^2}{2} + 2n) \\]

Figure 12-2   Bubble sort before and after array division

Next, we compute the following inequality, where the left and right sides represent the total number of operations before and after division, respectively:

\\[ \\begin{aligned} n^2 & > \\frac{n^2}{2} + 2n \\newline n^2 - \\frac{n^2}{2} - 2n & > 0 \\newline n(n - 4) & > 0 \\end{aligned} \\]

This means that when \\(n > 4\\), the number of operations after division is smaller, and sorting efficiency should be higher. Note that the time complexity after division is still quadratic \\(O(n^2)\\), but the constant term in the complexity has become smaller.

Going further, what if we continuously divide the subarrays from their midpoints into two subarrays until the subarrays have only one element? This approach is actually \"merge sort\", with a time complexity of \\(O(n \\log n)\\).

Thinking further, what if we set multiple division points and evenly divide the original array into \\(k\\) subarrays? This situation is very similar to \"bucket sort\", which is well-suited for sorting massive amounts of data, with a theoretical time complexity of \\(O(n + k)\\).

","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#2-parallel-computation-optimization","level":3,"title":"2.   Parallel Computation Optimization","text":"

We know that the subproblems generated by divide and conquer are independent of each other, so they can typically be solved in parallel. This means divide and conquer can not only reduce the time complexity of algorithms, but also benefits from parallel optimization by operating systems.

Parallel optimization is particularly effective in multi-core or multi-processor environments, as the system can simultaneously handle multiple subproblems, making fuller use of computing resources and significantly reducing overall runtime.

For example, in the \"bucket sort\" shown in Figure 12-3, we evenly distribute massive data into various buckets, and the sorting tasks for all buckets can be distributed to various computing units. After completion, the results are merged.

Figure 12-3   Parallel computation in bucket sort

","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1213-common-applications-of-divide-and-conquer","level":2,"title":"12.1.3   Common Applications of Divide and Conquer","text":"

On one hand, divide and conquer can be used to solve many classic algorithmic problems.

  • Finding the closest pair of points: This algorithm first divides the point set into two parts, then finds the closest pair of points in each part separately, and finally finds the closest pair of points that spans both parts.
  • Large integer multiplication: For example, the Karatsuba algorithm, which decomposes large integer multiplication into several smaller integer multiplications and additions.
  • Matrix multiplication: For example, the Strassen algorithm, which decomposes large matrix multiplication into multiple small matrix multiplications and additions.
  • Hanota problem: The hanota problem can be solved through recursion, which is a typical application of the divide and conquer strategy.
  • Solving inversion pairs: In a sequence, if a preceding number is greater than a following number, these two numbers form an inversion pair. Solving the inversion pair problem can utilize the divide and conquer approach with the help of merge sort.

On the other hand, divide and conquer is widely applied in the design of algorithms and data structures.

  • Binary search: Binary search divides a sorted array into two parts from the midpoint index, then decides which half to eliminate based on the comparison result between the target value and the middle element value, and performs the same binary operation on the remaining interval.
  • Merge sort: Already introduced at the beginning of this section, no further elaboration needed.
  • Quick sort: Quick sort selects a pivot value, then divides the array into two subarrays, one with elements smaller than the pivot and the other with elements larger than the pivot, then performs the same division operation on these two parts until the subarrays have only one element.
  • Bucket sort: The basic idea of bucket sort is to scatter data into multiple buckets, then sort the elements within each bucket, and finally extract the elements from each bucket in sequence to obtain a sorted array.
  • Trees: For example, binary search trees, AVL trees, red-black trees, B-trees, B+ trees, etc. Their search, insertion, and deletion operations can all be viewed as applications of the divide and conquer strategy.
  • Heaps: A heap is a special complete binary tree, and its various operations, such as insertion, deletion, and heapify, actually imply the divide and conquer idea.
  • Hash tables: Although hash tables do not directly apply divide and conquer, some hash collision resolution solutions indirectly apply the divide and conquer strategy. For example, long linked lists in chaining may be converted to red-black trees to improve query efficiency.

It can be seen that divide and conquer is a \"subtly pervasive\" algorithmic idea, embedded in various algorithms and data structures.

","path":["Chapter 12. Divide and Conquer","12.1   Divide and Conquer Algorithms"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/","level":1,"title":"12.4   Hanota Problem","text":"

In merge sort and building binary trees, we decompose the original problem into two subproblems, each half the size of the original problem. However, for the hanota problem, we adopt a different decomposition strategy.

Question

Given three pillars, denoted as A, B, and C. Initially, pillar A has \\(n\\) discs stacked on it, arranged from top to bottom in ascending order of size. Our task is to move these \\(n\\) discs to pillar C while maintaining their original order (as shown in Figure 12-10). The following rules must be followed when moving the discs.

  1. A disc can only be taken from the top of one pillar and placed on top of another pillar.
  2. Only one disc can be moved at a time.
  3. A smaller disc must always be on top of a larger disc.

Figure 12-10   Example of the hanota problem

We denote the hanota problem of size \\(i\\) as \\(f(i)\\). For example, \\(f(3)\\) represents moving \\(3\\) discs from A to C.

","path":["Chapter 12. Divide and Conquer","12.4   Hanota Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#1-considering-the-base-cases","level":3,"title":"1.   Considering the Base Cases","text":"

As shown in Figure 12-11, for problem \\(f(1)\\), when there is only one disc, we can move it directly from A to C.

<1><2>

Figure 12-11   Solution for a problem of size 1

As shown in Figure 12-12, for problem \\(f(2)\\), when there are two discs, since we must always keep the smaller disc on top of the larger disc, we need to use B to assist in the move.

  1. First, move the smaller disc from A to B.
  2. Then move the larger disc from A to C.
  3. Finally, move the smaller disc from B to C.
<1><2><3><4>

Figure 12-12   Solution for a problem of size 2

The process of solving problem \\(f(2)\\) can be summarized as: moving two discs from A to C with the help of B. Here, C is called the target pillar, and B is called the buffer pillar.

","path":["Chapter 12. Divide and Conquer","12.4   Hanota Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#2-subproblem-decomposition","level":3,"title":"2.   Subproblem Decomposition","text":"

For problem \\(f(3)\\), when there are three discs, the situation becomes slightly more complex.

Since we already know the solutions to \\(f(1)\\) and \\(f(2)\\), we can think from a divide and conquer perspective, treating the top two discs on A as a whole, and execute the steps shown in Figure 12-13. This successfully moves the three discs from A to C.

  1. Let B be the target pillar and C be the buffer pillar, and move two discs from A to B.
  2. Move the remaining disc from A directly to C.
  3. Let C be the target pillar and A be the buffer pillar, and move two discs from B to C.
<1><2><3><4>

Figure 12-13   Solution for a problem of size 3

Essentially, we divide problem \\(f(3)\\) into two subproblems \\(f(2)\\) and one subproblem \\(f(1)\\). By solving these three subproblems in order, the original problem is solved. This shows that the subproblems are independent and their solutions can be merged.

From this, we can summarize the divide and conquer strategy for solving the hanota problem shown in Figure 12-14: divide the original problem \\(f(n)\\) into two subproblems \\(f(n-1)\\) and one subproblem \\(f(1)\\), and solve these three subproblems in the following order.

  1. Move \\(n-1\\) discs from A to B with the help of C.
  2. Move the remaining \\(1\\) disc directly from A to C.
  3. Move \\(n-1\\) discs from B to C with the help of A.

For these two subproblems \\(f(n-1)\\), we can recursively divide them in the same way until reaching the smallest subproblem \\(f(1)\\). The solution to \\(f(1)\\) is known and requires only one move operation.

Figure 12-14   Divide and conquer strategy for solving the hanota problem

","path":["Chapter 12. Divide and Conquer","12.4   Hanota Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#3-code-implementation","level":3,"title":"3.   Code Implementation","text":"

In the code, we declare a recursive function dfs(i, src, buf, tar), whose purpose is to move the top \\(i\\) discs from pillar src to target pillar tar with the help of buffer pillar buf:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hanota.py
def move(src: list[int], tar: list[int]):\n    \"\"\"Move a disk\"\"\"\n    # Take out a disk from the top of src\n    pan = src.pop()\n    # Place the disk on top of tar\n    tar.append(pan)\n\ndef dfs(i: int, src: list[int], buf: list[int], tar: list[int]):\n    \"\"\"Solve the Tower of Hanoi problem f(i)\"\"\"\n    # If there is only one disk left in src, move it directly to tar\n    if i == 1:\n        move(src, tar)\n        return\n    # Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf)\n    # Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar)\n    # Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar)\n\ndef solve_hanota(A: list[int], B: list[int], C: list[int]):\n    \"\"\"Solve the Tower of Hanoi problem\"\"\"\n    n = len(A)\n    # Move the top n disks from A to C using B\n    dfs(n, A, B, C)\n
hanota.cpp
/* Move a disk */\nvoid move(vector<int> &src, vector<int> &tar) {\n    // Take out a disk from the top of src\n    int pan = src.back();\n    src.pop_back();\n    // Place the disk on top of tar\n    tar.push_back(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, vector<int> &src, vector<int> &buf, vector<int> &tar) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i == 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(vector<int> &A, vector<int> &B, vector<int> &C) {\n    int n = A.size();\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
hanota.java
/* Move a disk */\nvoid move(List<Integer> src, List<Integer> tar) {\n    // Take out a disk from the top of src\n    Integer pan = src.remove(src.size() - 1);\n    // Place the disk on top of tar\n    tar.add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, List<Integer> src, List<Integer> buf, List<Integer> tar) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i == 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(List<Integer> A, List<Integer> B, List<Integer> C) {\n    int n = A.size();\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
hanota.cs
/* Move a disk */\nvoid Move(List<int> src, List<int> tar) {\n    // Take out a disk from the top of src\n    int pan = src[^1];\n    src.RemoveAt(src.Count - 1);\n    // Place the disk on top of tar\n    tar.Add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid DFS(int i, List<int> src, List<int> buf, List<int> tar) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i == 1) {\n        Move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    DFS(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    Move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    DFS(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid SolveHanota(List<int> A, List<int> B, List<int> C) {\n    int n = A.Count;\n    // Move the top n disks from A to C using B\n    DFS(n, A, B, C);\n}\n
hanota.go
/* Move a disk */\nfunc move(src, tar *list.List) {\n    // Take out a disk from the top of src\n    pan := src.Back()\n    // Place the disk on top of tar\n    tar.PushBack(pan.Value)\n    // Remove top disk from src\n    src.Remove(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunc dfsHanota(i int, src, buf, tar *list.List) {\n    // If there is only one disk left in src, move it directly to tar\n    if i == 1 {\n        move(src, tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfsHanota(i-1, src, tar, buf)\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfsHanota(i-1, buf, src, tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfunc solveHanota(A, B, C *list.List) {\n    n := A.Len()\n    // Move the top n disks from A to C using B\n    dfsHanota(n, A, B, C)\n}\n
hanota.swift
/* Move a disk */\nfunc move(src: inout [Int], tar: inout [Int]) {\n    // Take out a disk from the top of src\n    let pan = src.popLast()!\n    // Place the disk on top of tar\n    tar.append(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunc dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) {\n    // If there is only one disk left in src, move it directly to tar\n    if i == 1 {\n        move(src: &src, tar: &tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i: i - 1, src: &src, buf: &tar, tar: &buf)\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src: &src, tar: &tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i: i - 1, src: &buf, buf: &src, tar: &tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfunc solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) {\n    let n = A.count\n    // The tail of the list is the top of the rod\n    // Move top n disks from src to C using B\n    dfs(i: n, src: &A, buf: &B, tar: &C)\n}\n
hanota.js
/* Move a disk */\nfunction move(src, tar) {\n    // Take out a disk from the top of src\n    const pan = src.pop();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunction dfs(i, src, buf, tar) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i === 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfunction solveHanota(A, B, C) {\n    const n = A.length;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
hanota.ts
/* Move a disk */\nfunction move(src: number[], tar: number[]): void {\n    // Take out a disk from the top of src\n    const pan = src.pop();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunction dfs(i: number, src: number[], buf: number[], tar: number[]): void {\n    // If there is only one disk left in src, move it directly to tar\n    if (i === 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfunction solveHanota(A: number[], B: number[], C: number[]): void {\n    const n = A.length;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
hanota.dart
/* Move a disk */\nvoid move(List<int> src, List<int> tar) {\n  // Take out a disk from the top of src\n  int pan = src.removeLast();\n  // Place the disk on top of tar\n  tar.add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, List<int> src, List<int> buf, List<int> tar) {\n  // If there is only one disk left in src, move it directly to tar\n  if (i == 1) {\n    move(src, tar);\n    return;\n  }\n  // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n  dfs(i - 1, src, tar, buf);\n  // Subproblem f(1): move the remaining disk from src to tar\n  move(src, tar);\n  // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n  dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(List<int> A, List<int> B, List<int> C) {\n  int n = A.length;\n  // Move the top n disks from A to C using B\n  dfs(n, A, B, C);\n}\n
hanota.rs
/* Move a disk */\nfn move_pan(src: &mut Vec<i32>, tar: &mut Vec<i32>) {\n    // Take out a disk from the top of src\n    let pan = src.pop().unwrap();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfn dfs(i: i32, src: &mut Vec<i32>, buf: &mut Vec<i32>, tar: &mut Vec<i32>) {\n    // If there is only one disk left in src, move it directly to tar\n    if i == 1 {\n        move_pan(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move_pan(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfn solve_hanota(A: &mut Vec<i32>, B: &mut Vec<i32>, C: &mut Vec<i32>) {\n    let n = A.len() as i32;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
hanota.c
/* Move a disk */\nvoid move(int *src, int *srcSize, int *tar, int *tarSize) {\n    // Take out a disk from the top of src\n    int pan = src[*srcSize - 1];\n    src[*srcSize - 1] = 0;\n    (*srcSize)--;\n    // Place the disk on top of tar\n    tar[*tarSize] = pan;\n    (*tarSize)++;\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i == 1) {\n        move(src, srcSize, tar, tarSize);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize);\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, srcSize, tar, tarSize);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) {\n    // Move the top n disks from A to C using B\n    dfs(*ASize, A, ASize, B, BSize, C, CSize);\n}\n
hanota.kt
/* Move a disk */\nfun move(src: MutableList<Int>, tar: MutableList<Int>) {\n    // Take out a disk from the top of src\n    val pan = src.removeAt(src.size - 1)\n    // Place the disk on top of tar\n    tar.add(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfun dfs(i: Int, src: MutableList<Int>, buf: MutableList<Int>, tar: MutableList<Int>) {\n    // If there is only one disk left in src, move it directly to tar\n    if (i == 1) {\n        move(src, tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf)\n    // Subproblem f(1): move the remaining disk from src to tar\n    move(src, tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfun solveHanota(A: MutableList<Int>, B: MutableList<Int>, C: MutableList<Int>) {\n    val n = A.size\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C)\n}\n
hanota.rb
### Move one disk ###\ndef move(src, tar)\n  # Take out a disk from the top of src\n  pan = src.pop\n  # Place the disk on top of tar\n  tar << pan\nend\n\n### Solve Tower of Hanoi f(i) ###\ndef dfs(i, src, buf, tar)\n  # If there is only one disk left in src, move it directly to tar\n  if i == 1\n    move(src, tar)\n    return\n  end\n\n  # Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n  dfs(i - 1, src, tar, buf)\n  # Subproblem f(1): move the remaining disk from src to tar\n  move(src, tar)\n  # Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n  dfs(i - 1, buf, src, tar)\nend\n\n### Solve Tower of Hanoi ###\ndef solve_hanota(_A, _B, _C)\n  n = _A.length\n  # Move the top n disks from A to C using B\n  dfs(n, _A, _B, _C)\nend\n

As shown in Figure 12-15, the hanota problem forms a recursion tree of height \\(n\\), where each node represents a subproblem corresponding to an invocation of the dfs() function, therefore the time complexity is \\(O(2^n)\\) and the space complexity is \\(O(n)\\).

Figure 12-15   Recursion tree of the hanota problem

Quote

The hanota problem originates from an ancient legend. In a temple in ancient India, monks had three tall diamond pillars and \\(64\\) golden discs of different sizes. The monks continuously moved the discs, believing that when the last disc was correctly placed, the world would come to an end.

However, even if the monks moved one disc per second, it would take approximately \\(2^{64} \\approx 1.84×10^{19}\\) seconds, which is about \\(5850\\) billion years, far exceeding current estimates of the age of the universe. Therefore, if this legend is true, we should not need to worry about the end of the world.

","path":["Chapter 12. Divide and Conquer","12.4   Hanota Problem"],"tags":[]},{"location":"chapter_divide_and_conquer/summary/","level":1,"title":"12.5   Summary","text":"","path":["Chapter 12. Divide and Conquer","12.5   Summary"],"tags":[]},{"location":"chapter_divide_and_conquer/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Divide and conquer is a common algorithm design strategy, consisting of two phases: divide (partition) and conquer (merge), typically implemented based on recursion.
  • The criteria for determining whether a problem is a divide and conquer problem include: whether the problem can be decomposed, whether subproblems are independent, and whether subproblems can be merged.
  • Merge sort is a typical application of the divide and conquer strategy. It recursively divides an array into two equal-length subarrays until only one element remains, then merges them layer by layer to complete the sorting.
  • Introducing the divide and conquer strategy can often improve algorithm efficiency. On one hand, the divide and conquer strategy reduces the number of operations; on the other hand, it facilitates parallel optimization of the system after division.
  • Divide and conquer can both solve many algorithmic problems and is widely applied in data structure and algorithm design, appearing everywhere.
  • Compared to brute-force search, adaptive search is more efficient. Search algorithms with time complexity of \\(O(\\log n)\\) are typically implemented based on the divide and conquer strategy.
  • Binary search is another typical application of divide and conquer. It does not include the step of merging solutions of subproblems. We can implement binary search through recursive divide and conquer.
  • In the problem of building a binary tree, building the tree (original problem) can be divided into building the left subtree and right subtree (subproblems), which can be achieved by dividing the index intervals of the preorder and inorder traversals.
  • In the hanota problem, a problem of size \\(n\\) can be divided into two subproblems of size \\(n-1\\) and one subproblem of size \\(1\\). After solving these three subproblems in order, the original problem is solved.
","path":["Chapter 12. Divide and Conquer","12.5   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/","level":1,"title":"Chapter 14.   Dynamic Programming","text":"

Abstract

Streams converge into rivers, rivers converge into the sea.

Dynamic programming gathers solutions to small problems into answers to large problems, step by step guiding us to the shore of problem-solving.

","path":["Chapter 14. Dynamic Programming","Chapter 14.   Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 14.1   Introduction to Dynamic Programming
  • 14.2   Characteristics of Dynamic Programming Problems
  • 14.3   Dynamic Programming Problem-Solving Approach
  • 14.4   0-1 Knapsack Problem
  • 14.5   Unbounded Knapsack Problem
  • 14.6   Edit Distance Problem
  • 14.7   Summary
","path":["Chapter 14. Dynamic Programming","Chapter 14.   Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/","level":1,"title":"14.2   Characteristics of Dynamic Programming Problems","text":"

In the previous section, we learned how dynamic programming solves the original problem by decomposing it into subproblems. In fact, subproblem decomposition is a general algorithmic approach, with different emphases in divide and conquer, dynamic programming, and backtracking.

  • Divide and conquer algorithms recursively divide the original problem into multiple independent subproblems until the smallest subproblems are reached, and merge the solutions to the subproblems during backtracking to ultimately obtain the solution to the original problem.
  • Dynamic programming also recursively decomposes problems, but the main difference from divide and conquer algorithms is that subproblems in dynamic programming are interdependent, and many overlapping subproblems appear during the decomposition process.
  • Backtracking algorithms enumerate all possible solutions through trial and error, and avoid unnecessary search branches through pruning. The solution to the original problem consists of a series of decision steps, and we can regard the subsequence before each decision step as a subproblem.

In fact, dynamic programming is commonly used to solve optimization problems, which not only contain overlapping subproblems but also have two other major characteristics: optimal substructure and no aftereffects.

","path":["Chapter 14. Dynamic Programming","14.2   Characteristics of Dynamic Programming Problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/#1421-optimal-substructure","level":2,"title":"14.2.1   Optimal Substructure","text":"

We make a slight modification to the stair climbing problem to make it more suitable for demonstrating the concept of optimal substructure.

Climbing stairs with minimum cost

Given a staircase, where you can climb \\(1\\) or \\(2\\) steps at a time, and each step has a non-negative integer representing the cost you need to pay at that step. Given a non-negative integer array \\(cost\\), where \\(cost[i]\\) represents the cost at the \\(i\\)-th step, and \\(cost[0]\\) is the ground (starting point). What is the minimum cost required to reach the top?

As shown in Figure 14-6, if the costs of the \\(1\\)st, \\(2\\)nd, and \\(3\\)rd steps are \\(1\\), \\(10\\), and \\(1\\) respectively, then climbing from the ground to the \\(3\\)rd step requires a minimum cost of \\(2\\).

Figure 14-6   Minimum cost to climb to the 3rd step

Let \\(dp[i]\\) be the accumulated cost of climbing to the \\(i\\)-th step. Since the \\(i\\)-th step can only come from the \\(i-1\\)-th or \\(i-2\\)-th step, \\(dp[i]\\) can only equal \\(dp[i-1] + cost[i]\\) or \\(dp[i-2] + cost[i]\\). To minimize the cost, we should choose the smaller of the two:

\\[ dp[i] = \\min(dp[i-1], dp[i-2]) + cost[i] \\]

This leads us to the meaning of optimal substructure: the optimal solution to the original problem is constructed from the optimal solutions to the subproblems.

This problem clearly has optimal substructure: we select the better one from the optimal solutions to the two subproblems \\(dp[i-1]\\) and \\(dp[i-2]\\), and use it to construct the optimal solution to the original problem \\(dp[i]\\).

So, does the stair climbing problem from the previous section have optimal substructure? Its goal is to find the number of ways, which seems to be a counting problem, but if we change the question: \"Find the maximum number of ways\". We surprisingly discover that although the problem before and after modification are equivalent, the optimal substructure has emerged: the maximum number of ways for the \\(n\\)-th step equals the sum of the maximum number of ways for the \\(n-1\\)-th and \\(n-2\\)-th steps. Therefore, the interpretation of optimal substructure is quite flexible and will have different meanings in different problems.

According to the state transition equation and the initial states \\(dp[1] = cost[1]\\) and \\(dp[2] = cost[2]\\), we can obtain the dynamic programming code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_cost_climbing_stairs_dp.py
def min_cost_climbing_stairs_dp(cost: list[int]) -> int:\n    \"\"\"Minimum cost climbing stairs: Dynamic programming\"\"\"\n    n = len(cost) - 1\n    if n == 1 or n == 2:\n        return cost[n]\n    # Initialize dp table, used to store solutions to subproblems\n    dp = [0] * (n + 1)\n    # Initial state: preset the solution to the smallest subproblem\n    dp[1], dp[2] = cost[1], cost[2]\n    # State transition: gradually solve larger subproblems from smaller ones\n    for i in range(3, n + 1):\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    return dp[n]\n
min_cost_climbing_stairs_dp.cpp
/* Minimum cost climbing stairs: Dynamic programming */\nint minCostClimbingStairsDP(vector<int> &cost) {\n    int n = cost.size() - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize dp table, used to store solutions to subproblems\n    vector<int> dp(n + 1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
min_cost_climbing_stairs_dp.java
/* Minimum cost climbing stairs: Dynamic programming */\nint minCostClimbingStairsDP(int[] cost) {\n    int n = cost.length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize dp table, used to store solutions to subproblems\n    int[] dp = new int[n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
min_cost_climbing_stairs_dp.cs
/* Minimum cost climbing stairs: Dynamic programming */\nint MinCostClimbingStairsDP(int[] cost) {\n    int n = cost.Length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize dp table, used to store solutions to subproblems\n    int[] dp = new int[n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
min_cost_climbing_stairs_dp.go
/* Minimum cost climbing stairs: Dynamic programming */\nfunc minCostClimbingStairsDP(cost []int) int {\n    n := len(cost) - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    min := func(a, b int) int {\n        if a < b {\n            return a\n        }\n        return b\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    dp := make([]int, n+1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i] = min(dp[i-1], dp[i-2]) + cost[i]\n    }\n    return dp[n]\n}\n
min_cost_climbing_stairs_dp.swift
/* Minimum cost climbing stairs: Dynamic programming */\nfunc minCostClimbingStairsDP(cost: [Int]) -> Int {\n    let n = cost.count - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    var dp = Array(repeating: 0, count: n + 1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3 ... n {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    }\n    return dp[n]\n}\n
min_cost_climbing_stairs_dp.js
/* Minimum cost climbing stairs: Dynamic programming */\nfunction minCostClimbingStairsDP(cost) {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = new Array(n + 1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
min_cost_climbing_stairs_dp.ts
/* Minimum cost climbing stairs: Dynamic programming */\nfunction minCostClimbingStairsDP(cost: Array<number>): number {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = new Array(n + 1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
min_cost_climbing_stairs_dp.dart
/* Minimum cost climbing stairs: Dynamic programming */\nint minCostClimbingStairsDP(List<int> cost) {\n  int n = cost.length - 1;\n  if (n == 1 || n == 2) return cost[n];\n  // Initialize dp table, used to store solutions to subproblems\n  List<int> dp = List.filled(n + 1, 0);\n  // Initial state: preset the solution to the smallest subproblem\n  dp[1] = cost[1];\n  dp[2] = cost[2];\n  // State transition: gradually solve larger subproblems from smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];\n  }\n  return dp[n];\n}\n
min_cost_climbing_stairs_dp.rs
/* Minimum cost climbing stairs: Dynamic programming */\nfn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 {\n    let n = cost.len() - 1;\n    if n == 1 || n == 2 {\n        return cost[n];\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    let mut dp = vec![-1; n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3..=n {\n        dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    dp[n]\n}\n
min_cost_climbing_stairs_dp.c
/* Minimum cost climbing stairs: Dynamic programming */\nint minCostClimbingStairsDP(int cost[], int costSize) {\n    int n = costSize - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize dp table, used to store solutions to subproblems\n    int *dp = calloc(n + 1, sizeof(int));\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    int res = dp[n];\n    // Free memory\n    free(dp);\n    return res;\n}\n
min_cost_climbing_stairs_dp.kt
/* Minimum cost climbing stairs: Dynamic programming */\nfun minCostClimbingStairsDP(cost: IntArray): Int {\n    val n = cost.size - 1\n    if (n == 1 || n == 2) return cost[n]\n    // Initialize dp table, used to store solutions to subproblems\n    val dp = IntArray(n + 1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (i in 3..n) {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    }\n    return dp[n]\n}\n
min_cost_climbing_stairs_dp.rb
### Minimum cost climbing stairs: DP ###\ndef min_cost_climbing_stairs_dp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  # Initialize dp table, used to store solutions to subproblems\n  dp = Array.new(n + 1, 0)\n  # Initial state: preset the solution to the smallest subproblem\n  dp[1], dp[2] = cost[1], cost[2]\n  # State transition: gradually solve larger subproblems from smaller ones\n  (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }\n  dp[n]\nend\n

Figure 14-7 shows the dynamic programming process for the above code.

Figure 14-7   Dynamic programming process for climbing stairs with minimum cost

This problem can also be space-optimized, compressing from one dimension to zero, reducing the space complexity from \\(O(n)\\) to \\(O(1)\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_cost_climbing_stairs_dp.py
def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int:\n    \"\"\"Minimum cost climbing stairs: Space-optimized dynamic programming\"\"\"\n    n = len(cost) - 1\n    if n == 1 or n == 2:\n        return cost[n]\n    a, b = cost[1], cost[2]\n    for i in range(3, n + 1):\n        a, b = b, min(a, b) + cost[i]\n    return b\n
min_cost_climbing_stairs_dp.cpp
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(vector<int> &cost) {\n    int n = cost.size() - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.java
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(int[] cost) {\n    int n = cost.length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.cs
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nint MinCostClimbingStairsDPComp(int[] cost) {\n    int n = cost.Length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = Math.Min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.go
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfunc minCostClimbingStairsDPComp(cost []int) int {\n    n := len(cost) - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    min := func(a, b int) int {\n        if a < b {\n            return a\n        }\n        return b\n    }\n    // Initial state: preset the solution to the smallest subproblem\n    a, b := cost[1], cost[2]\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i := 3; i <= n; i++ {\n        tmp := b\n        b = min(a, tmp) + cost[i]\n        a = tmp\n    }\n    return b\n}\n
min_cost_climbing_stairs_dp.swift
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfunc minCostClimbingStairsDPComp(cost: [Int]) -> Int {\n    let n = cost.count - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    var (a, b) = (cost[1], cost[2])\n    for i in 3 ... n {\n        (a, b) = (b, min(a, b) + cost[i])\n    }\n    return b\n}\n
min_cost_climbing_stairs_dp.js
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfunction minCostClimbingStairsDPComp(cost) {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    let a = cost[1],\n        b = cost[2];\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.ts
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfunction minCostClimbingStairsDPComp(cost: Array<number>): number {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    let a = cost[1],\n        b = cost[2];\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.dart
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(List<int> cost) {\n  int n = cost.length - 1;\n  if (n == 1 || n == 2) return cost[n];\n  int a = cost[1], b = cost[2];\n  for (int i = 3; i <= n; i++) {\n    int tmp = b;\n    b = min(a, tmp) + cost[i];\n    a = tmp;\n  }\n  return b;\n}\n
min_cost_climbing_stairs_dp.rs
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 {\n    let n = cost.len() - 1;\n    if n == 1 || n == 2 {\n        return cost[n];\n    };\n    let (mut a, mut b) = (cost[1], cost[2]);\n    for i in 3..=n {\n        let tmp = b;\n        b = cmp::min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    b\n}\n
min_cost_climbing_stairs_dp.c
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(int cost[], int costSize) {\n    int n = costSize - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = myMin(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
min_cost_climbing_stairs_dp.kt
/* Minimum cost climbing stairs: Space-optimized dynamic programming */\nfun minCostClimbingStairsDPComp(cost: IntArray): Int {\n    val n = cost.size - 1\n    if (n == 1 || n == 2) return cost[n]\n    var a = cost[1]\n    var b = cost[2]\n    for (i in 3..n) {\n        val tmp = b\n        b = min(a, tmp) + cost[i]\n        a = tmp\n    }\n    return b\n}\n
min_cost_climbing_stairs_dp.rb
### Minimum cost climbing stairs: DP ###\ndef min_cost_climbing_stairs_dp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  # Initialize dp table, used to store solutions to subproblems\n  dp = Array.new(n + 1, 0)\n  # Initial state: preset the solution to the smallest subproblem\n  dp[1], dp[2] = cost[1], cost[2]\n  # State transition: gradually solve larger subproblems from smaller ones\n  (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }\n  dp[n]\nend\n\n# Minimum cost climbing stairs: Space-optimized dynamic programming\ndef min_cost_climbing_stairs_dp_comp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  a, b = cost[1], cost[2]\n  (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] }\n  b\nend\n
","path":["Chapter 14. Dynamic Programming","14.2   Characteristics of Dynamic Programming Problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/#1422-no-aftereffects","level":2,"title":"14.2.2   No Aftereffects","text":"

No aftereffects is one of the important characteristics that enable dynamic programming to solve problems effectively. Its definition is: given a certain state, its future development is only related to the current state and has nothing to do with all past states.

Taking the stair climbing problem as an example, given state \\(i\\), it will develop into states \\(i+1\\) and \\(i+2\\), corresponding to jumping \\(1\\) step and jumping \\(2\\) steps, respectively. When making these two choices, we do not need to consider the states before state \\(i\\), as they have no effect on the future of state \\(i\\).

However, if we add a constraint to the stair climbing problem, the situation changes.

Climbing stairs with constraint

Given a staircase with \\(n\\) steps, where you can climb \\(1\\) or \\(2\\) steps at a time, but you cannot jump \\(1\\) step in two consecutive rounds. How many ways are there to climb to the top?

As shown in Figure 14-8, there are only \\(2\\) feasible ways to climb to the \\(3\\)rd step. The way of jumping \\(1\\) step three consecutive times does not satisfy the constraint and is therefore discarded.

Figure 14-8   Number of ways to climb to the 3rd step with constraint

In this problem, if the previous round was a jump of \\(1\\) step, then the next round must jump \\(2\\) steps. This means that the next choice cannot be determined solely by the current state (current stair step number), but also depends on the previous state (the stair step number from the previous round).

It is not difficult to see that this problem no longer satisfies no aftereffects, and the state transition equation \\(dp[i] = dp[i-1] + dp[i-2]\\) also fails, because \\(dp[i-1]\\) represents jumping \\(1\\) step in this round, but it includes many solutions where \"the previous round was a jump of \\(1\\) step\", which cannot be directly counted in \\(dp[i]\\) to satisfy the constraint.

For this reason, we need to expand the state definition: state \\([i, j]\\) represents being on the \\(i\\)-th step with the previous round having jumped \\(j\\) steps, where \\(j \\in \\{1, 2\\}\\). This state definition effectively distinguishes whether the previous round was a jump of \\(1\\) step or \\(2\\) steps, allowing us to determine where the current state came from.

  • When the previous round jumped \\(1\\) step, the round before that could only choose to jump \\(2\\) steps, i.e., \\(dp[i, 1]\\) can only be transferred from \\(dp[i-1, 2]\\).
  • When the previous round jumped \\(2\\) steps, the round before that could choose to jump \\(1\\) step or \\(2\\) steps, i.e., \\(dp[i, 2]\\) can be transferred from \\(dp[i-2, 1]\\) or \\(dp[i-2, 2]\\).

As shown in Figure 14-9, under this definition, \\(dp[i, j]\\) represents the number of ways for state \\([i, j]\\). The state transition equation is then:

\\[ \\begin{cases} dp[i, 1] = dp[i-1, 2] \\\\ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] \\end{cases} \\]

Figure 14-9   Recurrence relation considering constraints

Finally, return \\(dp[n, 1] + dp[n, 2]\\), where the sum of the two represents the total number of ways to climb to the \\(n\\)-th step:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_constraint_dp.py
def climbing_stairs_constraint_dp(n: int) -> int:\n    \"\"\"Climbing stairs with constraint: Dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return 1\n    # Initialize dp table, used to store solutions to subproblems\n    dp = [[0] * 3 for _ in range(n + 1)]\n    # Initial state: preset the solution to the smallest subproblem\n    dp[1][1], dp[1][2] = 1, 0\n    dp[2][1], dp[2][2] = 0, 1\n    # State transition: gradually solve larger subproblems from smaller ones\n    for i in range(3, n + 1):\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    return dp[n][1] + dp[n][2]\n
climbing_stairs_constraint_dp.cpp
/* Climbing stairs with constraint: Dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    vector<vector<int>> dp(n + 1, vector<int>(3, 0));\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
climbing_stairs_constraint_dp.java
/* Climbing stairs with constraint: Dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    int[][] dp = new int[n + 1][3];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
climbing_stairs_constraint_dp.cs
/* Climbing stairs with constraint: Dynamic programming */\nint ClimbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    int[,] dp = new int[n + 1, 3];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1, 1] = 1;\n    dp[1, 2] = 0;\n    dp[2, 1] = 0;\n    dp[2, 2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i, 1] = dp[i - 1, 2];\n        dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2];\n    }\n    return dp[n, 1] + dp[n, 2];\n}\n
climbing_stairs_constraint_dp.go
/* Climbing stairs with constraint: Dynamic programming */\nfunc climbingStairsConstraintDP(n int) int {\n    if n == 1 || n == 2 {\n        return 1\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    dp := make([][3]int, n+1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i][1] = dp[i-1][2]\n        dp[i][2] = dp[i-2][1] + dp[i-2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
climbing_stairs_constraint_dp.swift
/* Climbing stairs with constraint: Dynamic programming */\nfunc climbingStairsConstraintDP(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return 1\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3 ... n {\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
climbing_stairs_constraint_dp.js
/* Climbing stairs with constraint: Dynamic programming */\nfunction climbingStairsConstraintDP(n) {\n    if (n === 1 || n === 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = Array.from(new Array(n + 1), () => new Array(3));\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
climbing_stairs_constraint_dp.ts
/* Climbing stairs with constraint: Dynamic programming */\nfunction climbingStairsConstraintDP(n: number): number {\n    if (n === 1 || n === 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = Array.from({ length: n + 1 }, () => new Array(3));\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
climbing_stairs_constraint_dp.dart
/* Climbing stairs with constraint: Dynamic programming */\nint climbingStairsConstraintDP(int n) {\n  if (n == 1 || n == 2) {\n    return 1;\n  }\n  // Initialize dp table, used to store solutions to subproblems\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(3, 0));\n  // Initial state: preset the solution to the smallest subproblem\n  dp[1][1] = 1;\n  dp[1][2] = 0;\n  dp[2][1] = 0;\n  dp[2][2] = 1;\n  // State transition: gradually solve larger subproblems from smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i][1] = dp[i - 1][2];\n    dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n  }\n  return dp[n][1] + dp[n][2];\n}\n
climbing_stairs_constraint_dp.rs
/* Climbing stairs with constraint: Dynamic programming */\nfn climbing_stairs_constraint_dp(n: usize) -> i32 {\n    if n == 1 || n == 2 {\n        return 1;\n    };\n    // Initialize dp table, used to store solutions to subproblems\n    let mut dp = vec![vec![-1; 3]; n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3..=n {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    dp[n][1] + dp[n][2]\n}\n
climbing_stairs_constraint_dp.c
/* Climbing stairs with constraint: Dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(3, sizeof(int));\n    }\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    int res = dp[n][1] + dp[n][2];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
climbing_stairs_constraint_dp.kt
/* Climbing stairs with constraint: Dynamic programming */\nfun climbingStairsConstraintDP(n: Int): Int {\n    if (n == 1 || n == 2) {\n        return 1\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    val dp = Array(n + 1) { IntArray(3) }\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (i in 3..n) {\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
climbing_stairs_constraint_dp.rb
### Climbing stairs with constraint: DP ###\ndef climbing_stairs_constraint_dp(n)\n  return 1 if n == 1 || n == 2\n\n  # Initialize dp table, used to store solutions to subproblems\n  dp = Array.new(n + 1) { Array.new(3, 0) }\n  # Initial state: preset the solution to the smallest subproblem\n  dp[1][1], dp[1][2] = 1, 0\n  dp[2][1], dp[2][2] = 0, 1\n  # State transition: gradually solve larger subproblems from smaller ones\n  for i in 3...(n + 1)\n    dp[i][1] = dp[i - 1][2]\n    dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n  end\n\n  dp[n][1] + dp[n][2]\nend\n

In the above case, since we only need to consider one more preceding state, we can still make the problem satisfy no aftereffects by expanding the state definition. However, some problems have very severe \"aftereffects\".

Climbing stairs with obstacle generation

Given a staircase with \\(n\\) steps, where you can climb \\(1\\) or \\(2\\) steps at a time. It is stipulated that when climbing to the \\(i\\)-th step, the system will automatically place an obstacle on the \\(2i\\)-th step, and thereafter no round is allowed to jump to the \\(2i\\)-th step. For example, if the first two rounds jump to the \\(2\\)nd and \\(3\\)rd steps, then afterwards you cannot jump to the \\(4\\)th and \\(6\\)th steps. How many ways are there to climb to the top?

In this problem, the next jump depends on all past states, because each jump places obstacles on higher steps, affecting future jumps. For such problems, dynamic programming is often difficult to solve.

In fact, many complex combinatorial optimization problems (such as the traveling salesman problem) do not satisfy no aftereffects. For such problems, we usually choose to use other methods, such as heuristic search, genetic algorithms, reinforcement learning, etc., to obtain usable local optimal solutions within a limited time.

","path":["Chapter 14. Dynamic Programming","14.2   Characteristics of Dynamic Programming Problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/","level":1,"title":"14.3   Dynamic Programming Problem-Solving Approach","text":"

The previous two sections introduced the main characteristics of dynamic programming problems. Next, let us explore two more practical issues together.

  1. How to determine whether a problem is a dynamic programming problem?
  2. What is the complete process for solving a dynamic programming problem, and where should we start?
","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1431-problem-determination","level":2,"title":"14.3.1   Problem Determination","text":"

Generally speaking, if a problem contains overlapping subproblems, optimal substructure, and satisfies no aftereffects, then it is usually suitable for solving with dynamic programming. However, it is difficult to directly extract these characteristics from the problem description. Therefore, we usually relax the conditions and first observe whether the problem is suitable for solving with backtracking (exhaustive search).

Problems suitable for solving with backtracking usually satisfy the \"decision tree model\", which means the problem can be described using a tree structure, where each node represents a decision and each path represents a sequence of decisions.

In other words, if a problem contains an explicit concept of decisions, and the solution is generated through a series of decisions, then it satisfies the decision tree model and can usually be solved using backtracking.

On this basis, dynamic programming problems also have some \"bonus points\" for determination.

  • The problem contains descriptions such as maximum (minimum) or most (least), indicating optimization.
  • The problem's state can be represented using a list, multi-dimensional matrix, or tree, and a state has a recurrence relation with its surrounding states.

Correspondingly, there are also some \"penalty points\".

  • The goal of the problem is to find all possible solutions, rather than finding the optimal solution.
  • The problem description has obvious permutation and combination characteristics, requiring the return of specific multiple solutions.

If a problem satisfies the decision tree model and has relatively obvious \"bonus points\", we can assume it is a dynamic programming problem and verify it during the solving process.

","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1432-problem-solving-steps","level":2,"title":"14.3.2   Problem-Solving Steps","text":"

The problem-solving process for dynamic programming varies depending on the nature and difficulty of the problem, but generally follows these steps: describe decisions, define states, establish the \\(dp\\) table, derive state transition equations, determine boundary conditions, etc.

To illustrate the problem-solving steps more vividly, we use a classic problem \"minimum path sum\" as an example.

Question

Given an \\(n \\times m\\) two-dimensional grid grid, where each cell in the grid contains a non-negative integer representing the cost of that cell. A robot starts from the top-left cell and can only move down or right at each step until reaching the bottom-right cell. Return the minimum path sum from the top-left to the bottom-right.

Figure 14-10 shows an example where the minimum path sum for the given grid is \\(13\\).

Figure 14-10   Minimum path sum example data

Step 1: Think about the decisions in each round, define the state, and thus obtain the \\(dp\\) table

The decision in each round of this problem is to move one step down or right from the current cell. Let the row and column indices of the current cell be \\([i, j]\\). After moving down or right, the indices become \\([i+1, j]\\) or \\([i, j+1]\\). Therefore, the state should include two variables, the row index and column index, denoted as \\([i, j]\\).

State \\([i, j]\\) corresponds to the subproblem: the minimum path sum from the starting point \\([0, 0]\\) to \\([i, j]\\), denoted as \\(dp[i, j]\\).

From this, we obtain the two-dimensional \\(dp\\) matrix shown in Figure 14-11, whose size is the same as the input grid \\(grid\\).

Figure 14-11   State definition and dp table

Note

The dynamic programming and backtracking processes can be described as a sequence of decisions, and the state consists of all decision variables. It should contain all variables describing the progress of problem-solving, and should contain sufficient information to derive the next state.

Each state corresponds to a subproblem, and we define a \\(dp\\) table to store the solutions to all subproblems. Each independent variable of the state is a dimension of the \\(dp\\) table. Essentially, the \\(dp\\) table is a mapping between states and solutions to subproblems.

Step 2: Identify the optimal substructure, and then derive the state transition equation

For state \\([i, j]\\), it can only be transferred from the cell above \\([i-1, j]\\) or the cell to the left \\([i, j-1]\\). Therefore, the optimal substructure is: the minimum path sum to reach \\([i, j]\\) is determined by the smaller of the minimum path sums of \\([i, j-1]\\) and \\([i-1, j]\\).

Based on the above analysis, the state transition equation shown in Figure 14-12 can be derived:

\\[ dp[i, j] = \\min(dp[i-1, j], dp[i, j-1]) + grid[i, j] \\]

Figure 14-12   Optimal substructure and state transition equation

Note

Based on the defined \\(dp\\) table, think about the relationship between the original problem and subproblems, and find the method to construct the optimal solution to the original problem from the optimal solutions to the subproblems, which is the optimal substructure.

Once we identify the optimal substructure, we can use it to construct the state transition equation.

Step 3: Determine boundary conditions and state transition order

In this problem, states in the first row can only come from the state to their left, and states in the first column can only come from the state above them. Therefore, the first row \\(i = 0\\) and first column \\(j = 0\\) are boundary conditions.

As shown in Figure 14-13, since each cell is transferred from the cell to its left and the cell above it, we use loops to traverse the matrix, with the outer loop traversing rows and the inner loop traversing columns.

Figure 14-13   Boundary conditions and state transition order

Note

Boundary conditions in dynamic programming are used to initialize the \\(dp\\) table, and in search are used for pruning.

The core of state transition order is to ensure that when computing the solution to the current problem, all the smaller subproblems it depends on have already been computed correctly.

Based on the above analysis, we can directly write the dynamic programming code. However, subproblem decomposition is a top-down approach, so implementing in the order \"brute force search \\(\\rightarrow\\) memoization \\(\\rightarrow\\) dynamic programming\" is more aligned with thinking habits.

","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1-method-1-brute-force-search","level":3,"title":"1.   Method 1: Brute Force Search","text":"

Starting from state \\([i, j]\\), continuously decompose into smaller states \\([i-1, j]\\) and \\([i, j-1]\\). The recursive function includes the following elements.

  • Recursive parameters: state \\([i, j]\\).
  • Return value: minimum path sum from \\([0, 0]\\) to \\([i, j]\\), which is \\(dp[i, j]\\).
  • Termination condition: when \\(i = 0\\) and \\(j = 0\\), return cost \\(grid[0, 0]\\).
  • Pruning: when \\(i < 0\\) or \\(j < 0\\), the index is out of bounds, return cost \\(+\\infty\\), representing infeasibility.

The implementation code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int:\n    \"\"\"Minimum path sum: Brute-force search\"\"\"\n    # If it's the top-left cell, terminate the search\n    if i == 0 and j == 0:\n        return grid[0][0]\n    # If row or column index is out of bounds, return +∞ cost\n    if i < 0 or j < 0:\n        return inf\n    # Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    up = min_path_sum_dfs(grid, i - 1, j)\n    left = min_path_sum_dfs(grid, i, j - 1)\n    # Return the minimum path cost from top-left to (i, j)\n    return min(left, up) + grid[i][j]\n
min_path_sum.cpp
/* Minimum path sum: Brute-force search */\nint minPathSumDFS(vector<vector<int>> &grid, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;\n}\n
min_path_sum.java
/* Minimum path sum: Brute-force search */\nint minPathSumDFS(int[][] grid, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Integer.MAX_VALUE;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
min_path_sum.cs
/* Minimum path sum: Brute-force search */\nint MinPathSumDFS(int[][] grid, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return int.MaxValue;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    int up = MinPathSumDFS(grid, i - 1, j);\n    int left = MinPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return Math.Min(left, up) + grid[i][j];\n}\n
min_path_sum.go
/* Minimum path sum: Brute-force search */\nfunc minPathSumDFS(grid [][]int, i, j int) int {\n    // If it's the top-left cell, terminate the search\n    if i == 0 && j == 0 {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return math.MaxInt\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    up := minPathSumDFS(grid, i-1, j)\n    left := minPathSumDFS(grid, i, j-1)\n    // Return the minimum path cost from top-left to (i, j)\n    return int(math.Min(float64(left), float64(up))) + grid[i][j]\n}\n
min_path_sum.swift
/* Minimum path sum: Brute-force search */\nfunc minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int {\n    // If it's the top-left cell, terminate the search\n    if i == 0, j == 0 {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return .max\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    let up = minPathSumDFS(grid: grid, i: i - 1, j: j)\n    let left = minPathSumDFS(grid: grid, i: i, j: j - 1)\n    // Return the minimum path cost from top-left to (i, j)\n    return min(left, up) + grid[i][j]\n}\n
min_path_sum.js
/* Minimum path sum: Brute-force search */\nfunction minPathSumDFS(grid, i, j) {\n    // If it's the top-left cell, terminate the search\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    const up = minPathSumDFS(grid, i - 1, j);\n    const left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
min_path_sum.ts
/* Minimum path sum: Brute-force search */\nfunction minPathSumDFS(\n    grid: Array<Array<number>>,\n    i: number,\n    j: number\n): number {\n    // If it's the top-left cell, terminate the search\n    if (i === 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    const up = minPathSumDFS(grid, i - 1, j);\n    const left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
min_path_sum.dart
/* Minimum path sum: Brute-force search */\nint minPathSumDFS(List<List<int>> grid, int i, int j) {\n  // If it's the top-left cell, terminate the search\n  if (i == 0 && j == 0) {\n    return grid[0][0];\n  }\n  // If row or column index is out of bounds, return +∞ cost\n  if (i < 0 || j < 0) {\n    // In Dart, int type is fixed-range integer, no value representing \"infinity\"\n    return BigInt.from(2).pow(31).toInt();\n  }\n  // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n  int up = minPathSumDFS(grid, i - 1, j);\n  int left = minPathSumDFS(grid, i, j - 1);\n  // Return the minimum path cost from top-left to (i, j)\n  return min(left, up) + grid[i][j];\n}\n
min_path_sum.rs
/* Minimum path sum: Brute-force search */\nfn min_path_sum_dfs(grid: &Vec<Vec<i32>>, i: i32, j: i32) -> i32 {\n    // If it's the top-left cell, terminate the search\n    if i == 0 && j == 0 {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return i32::MAX;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    let up = min_path_sum_dfs(grid, i - 1, j);\n    let left = min_path_sum_dfs(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    std::cmp::min(left, up) + grid[i as usize][j as usize]\n}\n
min_path_sum.c
/* Minimum path sum: Brute-force search */\nint minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from top-left to (i, j)\n    return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;\n}\n
min_path_sum.kt
/* Minimum path sum: Brute-force search */\nfun minPathSumDFS(grid: Array<IntArray>, i: Int, j: Int): Int {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Int.MAX_VALUE\n    }\n    // Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n    val up = minPathSumDFS(grid, i - 1, j)\n    val left = minPathSumDFS(grid, i, j - 1)\n    // Return the minimum path cost from top-left to (i, j)\n    return min(left, up) + grid[i][j]\n}\n
min_path_sum.rb
### Minimum path sum: brute force search ###\ndef min_path_sum_dfs(grid, i, j)\n  # If it's the top-left cell, terminate the search\n  return grid[i][j] if i == 0 && j == 0\n  # If row or column index is out of bounds, return +∞ cost\n  return Float::INFINITY if i < 0 || j < 0\n  # Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)\n  up = min_path_sum_dfs(grid, i - 1, j)\n  left = min_path_sum_dfs(grid, i, j - 1)\n  # Return the minimum path cost from top-left to (i, j)\n  [left, up].min + grid[i][j]\nend\n

Figure 14-14 shows the recursion tree rooted at \\(dp[2, 1]\\), which includes some overlapping subproblems whose number will increase sharply as the size of grid grid grows.

Essentially, the reason for overlapping subproblems is: there are multiple paths from the top-left corner to reach a certain cell.

Figure 14-14   Brute force search recursion tree

Each state has two choices, down and right, so the total number of steps from the top-left corner to the bottom-right corner is \\(m + n - 2\\), giving a worst-case time complexity of \\(O(2^{m + n})\\), where \\(n\\) and \\(m\\) are the number of rows and columns of the grid, respectively. Note that this calculation does not account for situations near the grid boundaries, where only one choice remains when reaching the grid boundary, so the actual number of paths will be somewhat less.

","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#2-method-2-memoization","level":3,"title":"2.   Method 2: Memoization","text":"

We introduce a memo list mem of the same size as grid grid to record the solutions to subproblems and prune overlapping subproblems:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
def min_path_sum_dfs_mem(\n    grid: list[list[int]], mem: list[list[int]], i: int, j: int\n) -> int:\n    \"\"\"Minimum path sum: Memoization search\"\"\"\n    # If it's the top-left cell, terminate the search\n    if i == 0 and j == 0:\n        return grid[0][0]\n    # If row or column index is out of bounds, return +∞ cost\n    if i < 0 or j < 0:\n        return inf\n    # If there's a record, return it directly\n    if mem[i][j] != -1:\n        return mem[i][j]\n    # Minimum path cost for left and upper cells\n    up = min_path_sum_dfs_mem(grid, mem, i - 1, j)\n    left = min_path_sum_dfs_mem(grid, mem, i, j - 1)\n    # Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n
min_path_sum.cpp
/* Minimum path sum: Memoization search */\nint minPathSumDFSMem(vector<vector<int>> &grid, vector<vector<int>> &mem, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;\n    return mem[i][j];\n}\n
min_path_sum.java
/* Minimum path sum: Memoization search */\nint minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Integer.MAX_VALUE;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
min_path_sum.cs
/* Minimum path sum: Memoization search */\nint MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return int.MaxValue;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    int up = MinPathSumDFSMem(grid, mem, i - 1, j);\n    int left = MinPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = Math.Min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
min_path_sum.go
/* Minimum path sum: Memoization search */\nfunc minPathSumDFSMem(grid, mem [][]int, i, j int) int {\n    // If it's the top-left cell, terminate the search\n    if i == 0 && j == 0 {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return math.MaxInt\n    }\n    // If there's a record, return it directly\n    if mem[i][j] != -1 {\n        return mem[i][j]\n    }\n    // Minimum path cost for left and upper cells\n    up := minPathSumDFSMem(grid, mem, i-1, j)\n    left := minPathSumDFSMem(grid, mem, i, j-1)\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j]\n    return mem[i][j]\n}\n
min_path_sum.swift
/* Minimum path sum: Memoization search */\nfunc minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int {\n    // If it's the top-left cell, terminate the search\n    if i == 0, j == 0 {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return .max\n    }\n    // If there's a record, return it directly\n    if mem[i][j] != -1 {\n        return mem[i][j]\n    }\n    // Minimum path cost for left and upper cells\n    let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j)\n    let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1)\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n}\n
min_path_sum.js
/* Minimum path sum: Memoization search */\nfunction minPathSumDFSMem(grid, mem, i, j) {\n    // If it's the top-left cell, terminate the search\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] !== -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    const up = minPathSumDFSMem(grid, mem, i - 1, j);\n    const left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
min_path_sum.ts
/* Minimum path sum: Memoization search */\nfunction minPathSumDFSMem(\n    grid: Array<Array<number>>,\n    mem: Array<Array<number>>,\n    i: number,\n    j: number\n): number {\n    // If it's the top-left cell, terminate the search\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    const up = minPathSumDFSMem(grid, mem, i - 1, j);\n    const left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
min_path_sum.dart
/* Minimum path sum: Memoization search */\nint minPathSumDFSMem(List<List<int>> grid, List<List<int>> mem, int i, int j) {\n  // If it's the top-left cell, terminate the search\n  if (i == 0 && j == 0) {\n    return grid[0][0];\n  }\n  // If row or column index is out of bounds, return +∞ cost\n  if (i < 0 || j < 0) {\n    // In Dart, int type is fixed-range integer, no value representing \"infinity\"\n    return BigInt.from(2).pow(31).toInt();\n  }\n  // If there's a record, return it directly\n  if (mem[i][j] != -1) {\n    return mem[i][j];\n  }\n  // Minimum path cost for left and upper cells\n  int up = minPathSumDFSMem(grid, mem, i - 1, j);\n  int left = minPathSumDFSMem(grid, mem, i, j - 1);\n  // Record and return the minimum path cost from top-left to (i, j)\n  mem[i][j] = min(left, up) + grid[i][j];\n  return mem[i][j];\n}\n
min_path_sum.rs
/* Minimum path sum: Memoization search */\nfn min_path_sum_dfs_mem(grid: &Vec<Vec<i32>>, mem: &mut Vec<Vec<i32>>, i: i32, j: i32) -> i32 {\n    // If it's the top-left cell, terminate the search\n    if i == 0 && j == 0 {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if i < 0 || j < 0 {\n        return i32::MAX;\n    }\n    // If there's a record, return it directly\n    if mem[i as usize][j as usize] != -1 {\n        return mem[i as usize][j as usize];\n    }\n    // Minimum path cost for left and upper cells\n    let up = min_path_sum_dfs_mem(grid, mem, i - 1, j);\n    let left = min_path_sum_dfs_mem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize];\n    mem[i as usize][j as usize]\n}\n
min_path_sum.c
/* Minimum path sum: Memoization search */\nint minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path cost for left and upper cells\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;\n    return mem[i][j];\n}\n
min_path_sum.kt
/* Minimum path sum: Memoization search */\nfun minPathSumDFSMem(\n    grid: Array<IntArray>,\n    mem: Array<IntArray>,\n    i: Int,\n    j: Int\n): Int {\n    // If it's the top-left cell, terminate the search\n    if (i == 0 && j == 0) {\n        return grid[0][0]\n    }\n    // If row or column index is out of bounds, return +∞ cost\n    if (i < 0 || j < 0) {\n        return Int.MAX_VALUE\n    }\n    // If there's a record, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j]\n    }\n    // Minimum path cost for left and upper cells\n    val up = minPathSumDFSMem(grid, mem, i - 1, j)\n    val left = minPathSumDFSMem(grid, mem, i, j - 1)\n    // Record and return the minimum path cost from top-left to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n}\n
min_path_sum.rb
### Minimum path sum: memoization search ###\ndef min_path_sum_dfs_mem(grid, mem, i, j)\n  # If it's the top-left cell, terminate the search\n  return grid[0][0] if i == 0 && j == 0\n  # If row or column index is out of bounds, return +∞ cost\n  return Float::INFINITY if i < 0 || j < 0\n  # If there's a record, return it directly\n  return mem[i][j] if mem[i][j] != -1\n  # Minimum path cost for left and upper cells\n  up = min_path_sum_dfs_mem(grid, mem, i - 1, j)\n  left = min_path_sum_dfs_mem(grid, mem, i, j - 1)\n  # Record and return the minimum path cost from top-left to (i, j)\n  mem[i][j] = [left, up].min + grid[i][j]\nend\n

As shown in Figure 14-15, after introducing memoization, all subproblem solutions only need to be computed once, so the time complexity depends on the total number of states, which is the grid size \\(O(nm)\\).

Figure 14-15   Memoization recursion tree

","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#3-method-3-dynamic-programming","level":3,"title":"3.   Method 3: Dynamic Programming","text":"

Implement the dynamic programming solution based on iteration, as shown in the code below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
def min_path_sum_dp(grid: list[list[int]]) -> int:\n    \"\"\"Minimum path sum: Dynamic programming\"\"\"\n    n, m = len(grid), len(grid[0])\n    # Initialize dp table\n    dp = [[0] * m for _ in range(n)]\n    dp[0][0] = grid[0][0]\n    # State transition: first row\n    for j in range(1, m):\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    # State transition: first column\n    for i in range(1, n):\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    # State transition: rest of the rows and columns\n    for i in range(1, n):\n        for j in range(1, m):\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n    return dp[n - 1][m - 1]\n
min_path_sum.cpp
/* Minimum path sum: Dynamic programming */\nint minPathSumDP(vector<vector<int>> &grid) {\n    int n = grid.size(), m = grid[0].size();\n    // Initialize dp table\n    vector<vector<int>> dp(n, vector<int>(m));\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
min_path_sum.java
/* Minimum path sum: Dynamic programming */\nint minPathSumDP(int[][] grid) {\n    int n = grid.length, m = grid[0].length;\n    // Initialize dp table\n    int[][] dp = new int[n][m];\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
min_path_sum.cs
/* Minimum path sum: Dynamic programming */\nint MinPathSumDP(int[][] grid) {\n    int n = grid.Length, m = grid[0].Length;\n    // Initialize dp table\n    int[,] dp = new int[n, m];\n    dp[0, 0] = grid[0][0];\n    // State transition: first row\n    for (int j = 1; j < m; j++) {\n        dp[0, j] = dp[0, j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (int i = 1; i < n; i++) {\n        dp[i, 0] = dp[i - 1, 0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1, m - 1];\n}\n
min_path_sum.go
/* Minimum path sum: Dynamic programming */\nfunc minPathSumDP(grid [][]int) int {\n    n, m := len(grid), len(grid[0])\n    // Initialize dp table\n    dp := make([][]int, n)\n    for i := 0; i < n; i++ {\n        dp[i] = make([]int, m)\n    }\n    dp[0][0] = grid[0][0]\n    // State transition: first row\n    for j := 1; j < m; j++ {\n        dp[0][j] = dp[0][j-1] + grid[0][j]\n    }\n    // State transition: first column\n    for i := 1; i < n; i++ {\n        dp[i][0] = dp[i-1][0] + grid[i][0]\n    }\n    // State transition: rest of the rows and columns\n    for i := 1; i < n; i++ {\n        for j := 1; j < m; j++ {\n            dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j]\n        }\n    }\n    return dp[n-1][m-1]\n}\n
min_path_sum.swift
/* Minimum path sum: Dynamic programming */\nfunc minPathSumDP(grid: [[Int]]) -> Int {\n    let n = grid.count\n    let m = grid[0].count\n    // Initialize dp table\n    var dp = Array(repeating: Array(repeating: 0, count: m), count: n)\n    dp[0][0] = grid[0][0]\n    // State transition: first row\n    for j in 1 ..< m {\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    }\n    // State transition: first column\n    for i in 1 ..< n {\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    }\n    // State transition: rest of the rows and columns\n    for i in 1 ..< n {\n        for j in 1 ..< m {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n        }\n    }\n    return dp[n - 1][m - 1]\n}\n
min_path_sum.js
/* Minimum path sum: Dynamic programming */\nfunction minPathSumDP(grid) {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize dp table\n    const dp = Array.from({ length: n }, () =>\n        Array.from({ length: m }, () => 0)\n    );\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for (let j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (let i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i < n; i++) {\n        for (let j = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
min_path_sum.ts
/* Minimum path sum: Dynamic programming */\nfunction minPathSumDP(grid: Array<Array<number>>): number {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize dp table\n    const dp = Array.from({ length: n }, () =>\n        Array.from({ length: m }, () => 0)\n    );\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for (let j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (let i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i < n; i++) {\n        for (let j: number = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
min_path_sum.dart
/* Minimum path sum: Dynamic programming */\nint minPathSumDP(List<List<int>> grid) {\n  int n = grid.length, m = grid[0].length;\n  // Initialize dp table\n  List<List<int>> dp = List.generate(n, (i) => List.filled(m, 0));\n  dp[0][0] = grid[0][0];\n  // State transition: first row\n  for (int j = 1; j < m; j++) {\n    dp[0][j] = dp[0][j - 1] + grid[0][j];\n  }\n  // State transition: first column\n  for (int i = 1; i < n; i++) {\n    dp[i][0] = dp[i - 1][0] + grid[i][0];\n  }\n  // State transition: rest of the rows and columns\n  for (int i = 1; i < n; i++) {\n    for (int j = 1; j < m; j++) {\n      dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n    }\n  }\n  return dp[n - 1][m - 1];\n}\n
min_path_sum.rs
/* Minimum path sum: Dynamic programming */\nfn min_path_sum_dp(grid: &Vec<Vec<i32>>) -> i32 {\n    let (n, m) = (grid.len(), grid[0].len());\n    // Initialize dp table\n    let mut dp = vec![vec![0; m]; n];\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for j in 1..m {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for i in 1..n {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for i in 1..n {\n        for j in 1..m {\n            dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    dp[n - 1][m - 1]\n}\n
min_path_sum.c
/* Minimum path sum: Dynamic programming */\nint minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {\n    // Initialize dp table\n    int **dp = malloc(n * sizeof(int *));\n    for (int i = 0; i < n; i++) {\n        dp[i] = calloc(m, sizeof(int));\n    }\n    dp[0][0] = grid[0][0];\n    // State transition: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transition: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    int res = dp[n - 1][m - 1];\n    // Free memory\n    for (int i = 0; i < n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
min_path_sum.kt
/* Minimum path sum: Dynamic programming */\nfun minPathSumDP(grid: Array<IntArray>): Int {\n    val n = grid.size\n    val m = grid[0].size\n    // Initialize dp table\n    val dp = Array(n) { IntArray(m) }\n    dp[0][0] = grid[0][0]\n    // State transition: first row\n    for (j in 1..<m) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    }\n    // State transition: first column\n    for (i in 1..<n) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    }\n    // State transition: rest of the rows and columns\n    for (i in 1..<n) {\n        for (j in 1..<m) {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n        }\n    }\n    return dp[n - 1][m - 1]\n}\n
min_path_sum.rb
### Minimum path sum: dynamic programming ###\ndef min_path_sum_dp(grid)\n  n, m = grid.length, grid.first.length\n  # Initialize dp table\n  dp = Array.new(n) { Array.new(m, 0) }\n  dp[0][0] = grid[0][0]\n  # State transition: first row\n  (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] }\n  # State transition: first column\n  (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] }\n  # State transition: rest of the rows and columns\n  for i in 1...n\n    for j in 1...m\n      dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j]\n    end\n  end\n  dp[n -1][m -1]\nend\n

Figure 14-16 shows the state transition process for minimum path sum, which traverses the entire grid, thus the time complexity is \\(O(nm)\\).

The array dp has size \\(n \\times m\\), thus the space complexity is \\(O(nm)\\).

<1><2><3><4><5><6><7><8><9><10><11><12>

Figure 14-16   Dynamic programming process for minimum path sum

","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#4-space-optimization","level":3,"title":"4.   Space Optimization","text":"

Since each cell is only related to the cell to its left and the cell above it, we can use a single-row array to implement the \\(dp\\) table.

Note that since the array dp can only represent the state of one row, we cannot initialize the first column state in advance, but rather update it when traversing each row:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
def min_path_sum_dp_comp(grid: list[list[int]]) -> int:\n    \"\"\"Minimum path sum: Space-optimized dynamic programming\"\"\"\n    n, m = len(grid), len(grid[0])\n    # Initialize dp table\n    dp = [0] * m\n    # State transition: first row\n    dp[0] = grid[0][0]\n    for j in range(1, m):\n        dp[j] = dp[j - 1] + grid[0][j]\n    # State transition: rest of the rows\n    for i in range(1, n):\n        # State transition: first column\n        dp[0] = dp[0] + grid[i][0]\n        # State transition: rest of the columns\n        for j in range(1, m):\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n    return dp[m - 1]\n
min_path_sum.cpp
/* Minimum path sum: Space-optimized dynamic programming */\nint minPathSumDPComp(vector<vector<int>> &grid) {\n    int n = grid.size(), m = grid[0].size();\n    // Initialize dp table\n    vector<int> dp(m);\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
min_path_sum.java
/* Minimum path sum: Space-optimized dynamic programming */\nint minPathSumDPComp(int[][] grid) {\n    int n = grid.length, m = grid[0].length;\n    // Initialize dp table\n    int[] dp = new int[m];\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
min_path_sum.cs
/* Minimum path sum: Space-optimized dynamic programming */\nint MinPathSumDPComp(int[][] grid) {\n    int n = grid.Length, m = grid[0].Length;\n    // Initialize dp table\n    int[] dp = new int[m];\n    dp[0] = grid[0][0];\n    // State transition: first row\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
min_path_sum.go
/* Minimum path sum: Space-optimized dynamic programming */\nfunc minPathSumDPComp(grid [][]int) int {\n    n, m := len(grid), len(grid[0])\n    // Initialize dp table\n    dp := make([]int, m)\n    // State transition: first row\n    dp[0] = grid[0][0]\n    for j := 1; j < m; j++ {\n        dp[j] = dp[j-1] + grid[0][j]\n    }\n    // State transition: rest of the rows and columns\n    for i := 1; i < n; i++ {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transition: rest of the columns\n        for j := 1; j < m; j++ {\n            dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j]\n        }\n    }\n    return dp[m-1]\n}\n
min_path_sum.swift
/* Minimum path sum: Space-optimized dynamic programming */\nfunc minPathSumDPComp(grid: [[Int]]) -> Int {\n    let n = grid.count\n    let m = grid[0].count\n    // Initialize dp table\n    var dp = Array(repeating: 0, count: m)\n    // State transition: first row\n    dp[0] = grid[0][0]\n    for j in 1 ..< m {\n        dp[j] = dp[j - 1] + grid[0][j]\n    }\n    // State transition: rest of the rows\n    for i in 1 ..< n {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transition: rest of the columns\n        for j in 1 ..< m {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n        }\n    }\n    return dp[m - 1]\n}\n
min_path_sum.js
/* Minimum path sum: Space-optimized dynamic programming */\nfunction minPathSumDPComp(grid) {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize dp table\n    const dp = new Array(m);\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for (let j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (let i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (let j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
min_path_sum.ts
/* Minimum path sum: Space-optimized dynamic programming */\nfunction minPathSumDPComp(grid: Array<Array<number>>): number {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize dp table\n    const dp = new Array(m);\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for (let j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (let i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (let j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
min_path_sum.dart
/* Minimum path sum: Space-optimized dynamic programming */\nint minPathSumDPComp(List<List<int>> grid) {\n  int n = grid.length, m = grid[0].length;\n  // Initialize dp table\n  List<int> dp = List.filled(m, 0);\n  dp[0] = grid[0][0];\n  for (int j = 1; j < m; j++) {\n    dp[j] = dp[j - 1] + grid[0][j];\n  }\n  // State transition: rest of the rows\n  for (int i = 1; i < n; i++) {\n    // State transition: first column\n    dp[0] = dp[0] + grid[i][0];\n    // State transition: rest of the columns\n    for (int j = 1; j < m; j++) {\n      dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];\n    }\n  }\n  return dp[m - 1];\n}\n
min_path_sum.rs
/* Minimum path sum: Space-optimized dynamic programming */\nfn min_path_sum_dp_comp(grid: &Vec<Vec<i32>>) -> i32 {\n    let (n, m) = (grid.len(), grid[0].len());\n    // Initialize dp table\n    let mut dp = vec![0; m];\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for j in 1..m {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for i in 1..n {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for j in 1..m {\n            dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    dp[m - 1]\n}\n
min_path_sum.c
/* Minimum path sum: Space-optimized dynamic programming */\nint minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {\n    // Initialize dp table\n    int *dp = calloc(m, sizeof(int));\n    // State transition: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i < n; i++) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transition: rest of the columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    int res = dp[m - 1];\n    // Free memory\n    free(dp);\n    return res;\n}\n
min_path_sum.kt
/* Minimum path sum: Space-optimized dynamic programming */\nfun minPathSumDPComp(grid: Array<IntArray>): Int {\n    val n = grid.size\n    val m = grid[0].size\n    // Initialize dp table\n    val dp = IntArray(m)\n    // State transition: first row\n    dp[0] = grid[0][0]\n    for (j in 1..<m) {\n        dp[j] = dp[j - 1] + grid[0][j]\n    }\n    // State transition: rest of the rows\n    for (i in 1..<n) {\n        // State transition: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transition: rest of the columns\n        for (j in 1..<m) {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n        }\n    }\n    return dp[m - 1]\n}\n
min_path_sum.rb
### Minimum path sum: space-optimized DP ###\ndef min_path_sum_dp_comp(grid)\n  n, m = grid.length, grid.first.length\n  # Initialize dp table\n  dp = Array.new(m, 0)\n  # State transition: first row\n  dp[0] = grid[0][0]\n  (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] }\n  # State transition: rest of the rows\n  for i in 1...n\n    # State transition: first column\n    dp[0] = dp[0] + grid[i][0]\n    # State transition: rest of the columns\n    (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] }\n  end\n  dp[m - 1]\nend\n
","path":["Chapter 14. Dynamic Programming","14.3   Dynamic Programming Problem-Solving Approach"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/","level":1,"title":"14.6   Edit Distance Problem","text":"

Edit distance, also known as Levenshtein distance, refers to the minimum number of edits required to transform one string into another, commonly used in information retrieval and natural language processing to measure the similarity between two sequences.

Question

Given two strings \\(s\\) and \\(t\\), return the minimum number of edits required to transform \\(s\\) into \\(t\\).

You can perform three types of edit operations on a string: insert a character, delete a character, or replace a character with any other character.

As shown in Figure 14-27, transforming kitten into sitting requires 3 edits, including 2 replacements and 1 insertion; transforming hello into algo requires 3 steps, including 2 replacements and 1 deletion.

Figure 14-27   Example data for edit distance

The edit distance problem can be naturally explained using the decision tree model. Strings correspond to tree nodes, and a round of decision (one edit operation) corresponds to an edge of the tree.

As shown in Figure 14-28, without restricting operations, each node can branch into many edges, with each edge corresponding to one operation, meaning there are many possible paths to transform hello into algo.

From the perspective of the decision tree, the goal of this problem is to find the shortest path between node hello and node algo.

Figure 14-28   Representing edit distance problem based on decision tree model

","path":["Chapter 14. Dynamic Programming","14.6   Edit Distance Problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#1-dynamic-programming-approach","level":3,"title":"1.   Dynamic Programming Approach","text":"

Step 1: Think about the decisions in each round, define the state, and thus obtain the \\(dp\\) table

Each round of decision involves performing one edit operation on string \\(s\\).

We want the problem scale to gradually decrease during the editing process, which allows us to construct subproblems. Let the lengths of strings \\(s\\) and \\(t\\) be \\(n\\) and \\(m\\) respectively. We first consider the tail characters of the two strings, \\(s[n-1]\\) and \\(t[m-1]\\).

  • If \\(s[n-1]\\) and \\(t[m-1]\\) are the same, we can skip them and directly consider \\(s[n-2]\\) and \\(t[m-2]\\).
  • If \\(s[n-1]\\) and \\(t[m-1]\\) are different, we need to perform one edit on \\(s\\) (insert, delete, or replace) to make the tail characters of the two strings the same, allowing us to skip them and consider a smaller-scale problem.

In other words, each round of decision (edit operation) we make on string \\(s\\) will change the remaining characters to be matched in \\(s\\) and \\(t\\). Therefore, the state is the \\(i\\)-th and \\(j\\)-th characters currently being considered in \\(s\\) and \\(t\\), denoted as \\([i, j]\\).

State \\([i, j]\\) corresponds to the subproblem: the minimum number of edits required to change the first \\(i\\) characters of \\(s\\) into the first \\(j\\) characters of \\(t\\).

From this, we obtain a two-dimensional \\(dp\\) table of size \\((i+1) \\times (j+1)\\).

Step 2: Identify the optimal substructure, and then derive the state transition equation

Consider subproblem \\(dp[i, j]\\), where the tail characters of the corresponding two strings are \\(s[i-1]\\) and \\(t[j-1]\\), which can be divided into the three cases shown in Figure 14-29 based on different edit operations.

  1. Insert \\(t[j-1]\\) after \\(s[i-1]\\), then the remaining subproblem is \\(dp[i, j-1]\\).
  2. Delete \\(s[i-1]\\), then the remaining subproblem is \\(dp[i-1, j]\\).
  3. Replace \\(s[i-1]\\) with \\(t[j-1]\\), then the remaining subproblem is \\(dp[i-1, j-1]\\).

Figure 14-29   State transition for edit distance

Based on the above analysis, the optimal substructure can be obtained: the minimum number of edits for \\(dp[i, j]\\) equals the minimum among the minimum edit steps of \\(dp[i, j-1]\\), \\(dp[i-1, j]\\), and \\(dp[i-1, j-1]\\), plus the edit step \\(1\\) for this time. The corresponding state transition equation is:

\\[ dp[i, j] = \\min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 \\]

Please note that when \\(s[i-1]\\) and \\(t[j-1]\\) are the same, no edit is required for the current character, in which case the state transition equation is:

\\[ dp[i, j] = dp[i-1, j-1] \\]

Step 3: Determine boundary conditions and state transition order

When both strings are empty, the number of edit steps is \\(0\\), i.e., \\(dp[0, 0] = 0\\). When \\(s\\) is empty but \\(t\\) is not, the minimum number of edit steps equals the length of \\(t\\), i.e., the first row \\(dp[0, j] = j\\). When \\(s\\) is not empty but \\(t\\) is empty, the minimum number of edit steps equals the length of \\(s\\), i.e., the first column \\(dp[i, 0] = i\\).

Observing the state transition equation, the solution \\(dp[i, j]\\) depends on solutions to the left, above, and upper-left, so the entire \\(dp\\) table can be traversed in order through two nested loops.

","path":["Chapter 14. Dynamic Programming","14.6   Edit Distance Problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby edit_distance.py
def edit_distance_dp(s: str, t: str) -> int:\n    \"\"\"Edit distance: Dynamic programming\"\"\"\n    n, m = len(s), len(t)\n    dp = [[0] * (m + 1) for _ in range(n + 1)]\n    # State transition: first row and first column\n    for i in range(1, n + 1):\n        dp[i][0] = i\n    for j in range(1, m + 1):\n        dp[0][j] = j\n    # State transition: rest of the rows and columns\n    for i in range(1, n + 1):\n        for j in range(1, m + 1):\n            if s[i - 1] == t[j - 1]:\n                # If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1]\n            else:\n                # Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1\n    return dp[n][m]\n
edit_distance.cpp
/* Edit distance: Dynamic programming */\nint editDistanceDP(string s, string t) {\n    int n = s.length(), m = t.length();\n    vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));\n    // State transition: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
edit_distance.java
/* Edit distance: Dynamic programming */\nint editDistanceDP(String s, String t) {\n    int n = s.length(), m = t.length();\n    int[][] dp = new int[n + 1][m + 1];\n    // State transition: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) == t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
edit_distance.cs
/* Edit distance: Dynamic programming */\nint EditDistanceDP(string s, string t) {\n    int n = s.Length, m = t.Length;\n    int[,] dp = new int[n + 1, m + 1];\n    // State transition: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i, 0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0, j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[i, j] = dp[i - 1, j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n, m];\n}\n
edit_distance.go
/* Edit distance: Dynamic programming */\nfunc editDistanceDP(s string, t string) int {\n    n := len(s)\n    m := len(t)\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, m+1)\n    }\n    // State transition: first row and first column\n    for i := 1; i <= n; i++ {\n        dp[i][0] = i\n    }\n    for j := 1; j <= m; j++ {\n        dp[0][j] = j\n    }\n    // State transition: rest of the rows and columns\n    for i := 1; i <= n; i++ {\n        for j := 1; j <= m; j++ {\n            if s[i-1] == t[j-1] {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i-1][j-1]\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
edit_distance.swift
/* Edit distance: Dynamic programming */\nfunc editDistanceDP(s: String, t: String) -> Int {\n    let n = s.utf8CString.count\n    let m = t.utf8CString.count\n    var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1)\n    // State transition: first row and first column\n    for i in 1 ... n {\n        dp[i][0] = i\n    }\n    for j in 1 ... m {\n        dp[0][j] = j\n    }\n    // State transition: rest of the rows and columns\n    for i in 1 ... n {\n        for j in 1 ... m {\n            if s.utf8CString[i - 1] == t.utf8CString[j - 1] {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1]\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
edit_distance.js
/* Edit distance: Dynamic programming */\nfunction editDistanceDP(s, t) {\n    const n = s.length,\n        m = t.length;\n    const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));\n    // State transition: first row and first column\n    for (let i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (let j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] =\n                    Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
edit_distance.ts
/* Edit distance: Dynamic programming */\nfunction editDistanceDP(s: string, t: string): number {\n    const n = s.length,\n        m = t.length;\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: m + 1 }, () => 0)\n    );\n    // State transition: first row and first column\n    for (let i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (let j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] =\n                    Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
edit_distance.dart
/* Edit distance: Dynamic programming */\nint editDistanceDP(String s, String t) {\n  int n = s.length, m = t.length;\n  List<List<int>> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0));\n  // State transition: first row and first column\n  for (int i = 1; i <= n; i++) {\n    dp[i][0] = i;\n  }\n  for (int j = 1; j <= m; j++) {\n    dp[0][j] = j;\n  }\n  // State transition: rest of the rows and columns\n  for (int i = 1; i <= n; i++) {\n    for (int j = 1; j <= m; j++) {\n      if (s[i - 1] == t[j - 1]) {\n        // If two characters are equal, skip both characters\n        dp[i][j] = dp[i - 1][j - 1];\n      } else {\n        // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n        dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n      }\n    }\n  }\n  return dp[n][m];\n}\n
edit_distance.rs
/* Edit distance: Dynamic programming */\nfn edit_distance_dp(s: &str, t: &str) -> i32 {\n    let (n, m) = (s.len(), t.len());\n    let mut dp = vec![vec![0; m + 1]; n + 1];\n    // State transition: first row and first column\n    for i in 1..=n {\n        dp[i][0] = i as i32;\n    }\n    for j in 1..m {\n        dp[0][j] = j as i32;\n    }\n    // State transition: rest of the rows and columns\n    for i in 1..=n {\n        for j in 1..=m {\n            if s.chars().nth(i - 1) == t.chars().nth(j - 1) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] =\n                    std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    dp[n][m]\n}\n
edit_distance.c
/* Edit distance: Dynamic programming */\nint editDistanceDP(char *s, char *t, int n, int m) {\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(m + 1, sizeof(int));\n    }\n    // State transition: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    int res = dp[n][m];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
edit_distance.kt
/* Edit distance: Dynamic programming */\nfun editDistanceDP(s: String, t: String): Int {\n    val n = s.length\n    val m = t.length\n    val dp = Array(n + 1) { IntArray(m + 1) }\n    // State transition: first row and first column\n    for (i in 1..n) {\n        dp[i][0] = i\n    }\n    for (j in 1..m) {\n        dp[0][j] = j\n    }\n    // State transition: rest of the rows and columns\n    for (i in 1..n) {\n        for (j in 1..m) {\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[i][j] = dp[i - 1][j - 1]\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
edit_distance.rb
### Edit distance: dynamic programming ###\ndef edit_distance_dp(s, t)\n  n, m = s.length, t.length\n  dp = Array.new(n + 1) { Array.new(m + 1, 0) }\n  # State transition: first row and first column\n  (1...(n + 1)).each { |i| dp[i][0] = i }\n  (1...(m + 1)).each { |j| dp[0][j] = j }\n  # State transition: rest of the rows and columns\n  for i in 1...(n + 1)\n    for j in 1...(m +1)\n      if s[i - 1] == t[j - 1]\n        # If two characters are equal, skip both characters\n        dp[i][j] = dp[i - 1][j - 1]\n      else\n        # Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n        dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1\n      end\n    end\n  end\n  dp[n][m]\nend\n

As shown in Figure 14-30, the state transition process for the edit distance problem is very similar to the knapsack problem and can both be viewed as the process of filling a two-dimensional grid.

<1><2><3><4><5><6><7><8><9><10><11><12><13><14><15>

Figure 14-30   Dynamic programming process for edit distance

","path":["Chapter 14. Dynamic Programming","14.6   Edit Distance Problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#3-space-optimization","level":3,"title":"3.   Space Optimization","text":"

Since \\(dp[i, j]\\) is transferred from the solutions above \\(dp[i-1, j]\\), to the left \\(dp[i, j-1]\\), and to the upper-left \\(dp[i-1, j-1]\\), forward traversal will lose the upper-left solution \\(dp[i-1, j-1]\\), and reverse traversal cannot build \\(dp[i, j-1]\\) in advance, so neither traversal order is feasible.

For this reason, we can use a variable leftup to temporarily store the upper-left solution \\(dp[i-1, j-1]\\), so we only need to consider the solutions to the left and above. This situation is the same as the unbounded knapsack problem, allowing for forward traversal. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby edit_distance.py
def edit_distance_dp_comp(s: str, t: str) -> int:\n    \"\"\"Edit distance: Space-optimized dynamic programming\"\"\"\n    n, m = len(s), len(t)\n    dp = [0] * (m + 1)\n    # State transition: first row\n    for j in range(1, m + 1):\n        dp[j] = j\n    # State transition: rest of the rows\n    for i in range(1, n + 1):\n        # State transition: first column\n        leftup = dp[0]  # Temporarily store dp[i-1, j-1]\n        dp[0] += 1\n        # State transition: rest of the columns\n        for j in range(1, m + 1):\n            temp = dp[j]\n            if s[i - 1] == t[j - 1]:\n                # If two characters are equal, skip both characters\n                dp[j] = leftup\n            else:\n                # Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = min(dp[j - 1], dp[j], leftup) + 1\n            leftup = temp  # Update for next round's dp[i-1, j-1]\n    return dp[m]\n
edit_distance.cpp
/* Edit distance: Space-optimized dynamic programming */\nint editDistanceDPComp(string s, string t) {\n    int n = s.length(), m = t.length();\n    vector<int> dp(m + 1, 0);\n    // State transition: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i <= n; i++) {\n        // State transition: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m];\n}\n
edit_distance.java
/* Edit distance: Space-optimized dynamic programming */\nint editDistanceDPComp(String s, String t) {\n    int n = s.length(), m = t.length();\n    int[] dp = new int[m + 1];\n    // State transition: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i <= n; i++) {\n        // State transition: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s.charAt(i - 1) == t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m];\n}\n
edit_distance.cs
/* Edit distance: Space-optimized dynamic programming */\nint EditDistanceDPComp(string s, string t) {\n    int n = s.Length, m = t.Length;\n    int[] dp = new int[m + 1];\n    // State transition: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i <= n; i++) {\n        // State transition: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m];\n}\n
edit_distance.go
/* Edit distance: Space-optimized dynamic programming */\nfunc editDistanceDPComp(s string, t string) int {\n    n := len(s)\n    m := len(t)\n    dp := make([]int, m+1)\n    // State transition: first row\n    for j := 1; j <= m; j++ {\n        dp[j] = j\n    }\n    // State transition: rest of the rows\n    for i := 1; i <= n; i++ {\n        // State transition: first column\n        leftUp := dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transition: rest of the columns\n        for j := 1; j <= m; j++ {\n            temp := dp[j]\n            if s[i-1] == t[j-1] {\n                // If two characters are equal, skip both characters\n                dp[j] = leftUp\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1\n            }\n            leftUp = temp // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m]\n}\n
edit_distance.swift
/* Edit distance: Space-optimized dynamic programming */\nfunc editDistanceDPComp(s: String, t: String) -> Int {\n    let n = s.utf8CString.count\n    let m = t.utf8CString.count\n    var dp = Array(repeating: 0, count: m + 1)\n    // State transition: first row\n    for j in 1 ... m {\n        dp[j] = j\n    }\n    // State transition: rest of the rows\n    for i in 1 ... n {\n        // State transition: first column\n        var leftup = dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transition: rest of the columns\n        for j in 1 ... m {\n            let temp = dp[j]\n            if s.utf8CString[i - 1] == t.utf8CString[j - 1] {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1\n            }\n            leftup = temp // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m]\n}\n
edit_distance.js
/* Edit distance: Space-optimized dynamic programming */\nfunction editDistanceDPComp(s, t) {\n    const n = s.length,\n        m = t.length;\n    const dp = new Array(m + 1).fill(0);\n    // State transition: first row\n    for (let j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (let i = 1; i <= n; i++) {\n        // State transition: first column\n        let leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (let j = 1; j <= m; j++) {\n            const temp = dp[j];\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m];\n}\n
edit_distance.ts
/* Edit distance: Space-optimized dynamic programming */\nfunction editDistanceDPComp(s: string, t: string): number {\n    const n = s.length,\n        m = t.length;\n    const dp = new Array(m + 1).fill(0);\n    // State transition: first row\n    for (let j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (let i = 1; i <= n; i++) {\n        // State transition: first column\n        let leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (let j = 1; j <= m; j++) {\n            const temp = dp[j];\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m];\n}\n
edit_distance.dart
/* Edit distance: Space-optimized dynamic programming */\nint editDistanceDPComp(String s, String t) {\n  int n = s.length, m = t.length;\n  List<int> dp = List.filled(m + 1, 0);\n  // State transition: first row\n  for (int j = 1; j <= m; j++) {\n    dp[j] = j;\n  }\n  // State transition: rest of the rows\n  for (int i = 1; i <= n; i++) {\n    // State transition: first column\n    int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n    dp[0] = i;\n    // State transition: rest of the columns\n    for (int j = 1; j <= m; j++) {\n      int temp = dp[j];\n      if (s[i - 1] == t[j - 1]) {\n        // If two characters are equal, skip both characters\n        dp[j] = leftup;\n      } else {\n        // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n        dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;\n      }\n      leftup = temp; // Update for next round's dp[i-1, j-1]\n    }\n  }\n  return dp[m];\n}\n
edit_distance.rs
/* Edit distance: Space-optimized dynamic programming */\nfn edit_distance_dp_comp(s: &str, t: &str) -> i32 {\n    let (n, m) = (s.len(), t.len());\n    let mut dp = vec![0; m + 1];\n    // State transition: first row\n    for j in 1..m {\n        dp[j] = j as i32;\n    }\n    // State transition: rest of the rows\n    for i in 1..=n {\n        // State transition: first column\n        let mut leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i as i32;\n        // State transition: rest of the columns\n        for j in 1..=m {\n            let temp = dp[j];\n            if s.chars().nth(i - 1) == t.chars().nth(j - 1) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    dp[m]\n}\n
edit_distance.c
/* Edit distance: Space-optimized dynamic programming */\nint editDistanceDPComp(char *s, char *t, int n, int m) {\n    int *dp = calloc(m + 1, sizeof(int));\n    // State transition: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transition: rest of the rows\n    for (int i = 1; i <= n; i++) {\n        // State transition: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transition: rest of the columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup;\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update for next round's dp[i-1, j-1]\n        }\n    }\n    int res = dp[m];\n    // Free memory\n    free(dp);\n    return res;\n}\n
edit_distance.kt
/* Edit distance: Space-optimized dynamic programming */\nfun editDistanceDPComp(s: String, t: String): Int {\n    val n = s.length\n    val m = t.length\n    val dp = IntArray(m + 1)\n    // State transition: first row\n    for (j in 1..m) {\n        dp[j] = j\n    }\n    // State transition: rest of the rows\n    for (i in 1..n) {\n        // State transition: first column\n        var leftup = dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transition: rest of the columns\n        for (j in 1..m) {\n            val temp = dp[j]\n            if (s[i - 1] == t[j - 1]) {\n                // If two characters are equal, skip both characters\n                dp[j] = leftup\n            } else {\n                // Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1\n            }\n            leftup = temp // Update for next round's dp[i-1, j-1]\n        }\n    }\n    return dp[m]\n}\n
edit_distance.rb
### Edit distance: space-optimized DP ###\ndef edit_distance_dp_comp(s, t)\n  n, m = s.length, t.length\n  dp = Array.new(m + 1, 0)\n  # State transition: first row\n  (1...(m + 1)).each { |j| dp[j] = j }\n  # State transition: rest of the rows\n  for i in 1...(n + 1)\n    # State transition: first column\n    leftup = dp.first # Temporarily store dp[i-1, j-1]\n    dp[0] += 1\n    # State transition: rest of the columns\n    for j in 1...(m + 1)\n      temp = dp[j]\n      if s[i - 1] == t[j - 1]\n        # If two characters are equal, skip both characters\n        dp[j] = leftup\n      else\n        # Minimum edit steps = minimum edit steps of insert, delete, replace + 1\n        dp[j] = [dp[j - 1], dp[j], leftup].min + 1\n      end\n      leftup = temp # Update for next round's dp[i-1, j-1]\n    end\n  end\n  dp[m]\nend\n
","path":["Chapter 14. Dynamic Programming","14.6   Edit Distance Problem"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/","level":1,"title":"14.1   Introduction to Dynamic Programming","text":"

Dynamic programming is an important algorithmic paradigm that decomposes a problem into a series of smaller subproblems and avoids redundant computation by storing the solutions to subproblems, thereby significantly improving time efficiency.

In this section, we start with a classic example, first presenting its brute force backtracking solution, observing the overlapping subproblems within it, and then gradually deriving a more efficient dynamic programming solution.

Climbing stairs

Given a staircase with \\(n\\) steps, where you can climb \\(1\\) or \\(2\\) steps at a time, how many different ways are there to reach the top?

As shown in Figure 14-1, for a \\(3\\)-step staircase, there are \\(3\\) different ways to reach the top.

Figure 14-1   Number of ways to reach the 3rd step

The goal of this problem is to find the number of ways, we can consider using backtracking to enumerate all possibilities. Specifically, imagine climbing stairs as a multi-round selection process: starting from the ground, choosing to go up \\(1\\) or \\(2\\) steps in each round, incrementing the count by \\(1\\) whenever the top of the stairs is reached, and pruning when exceeding the top. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_backtrack.py
def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int:\n    \"\"\"Backtracking\"\"\"\n    # When climbing to the n-th stair, add 1 to the solution count\n    if state == n:\n        res[0] += 1\n    # Traverse all choices\n    for choice in choices:\n        # Pruning: not allowed to go beyond the n-th stair\n        if state + choice > n:\n            continue\n        # Attempt: make a choice, update state\n        backtrack(choices, state + choice, n, res)\n        # Backtrack\n\ndef climbing_stairs_backtrack(n: int) -> int:\n    \"\"\"Climbing stairs: Backtracking\"\"\"\n    choices = [1, 2]  # Can choose to climb up 1 or 2 stairs\n    state = 0  # Start climbing from the 0-th stair\n    res = [0]  # Use res[0] to record the solution count\n    backtrack(choices, state, n, res)\n    return res[0]\n
climbing_stairs_backtrack.cpp
/* Backtracking */\nvoid backtrack(vector<int> &choices, int state, int n, vector<int> &res) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state == n)\n        res[0]++;\n    // Traverse all choices\n    for (auto &choice : choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n)\n            continue;\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nint climbingStairsBacktrack(int n) {\n    vector<int> choices = {1, 2}; // Can choose to climb up 1 or 2 stairs\n    int state = 0;                // Start climbing from the 0-th stair\n    vector<int> res = {0};        // Use res[0] to record the solution count\n    backtrack(choices, state, n, res);\n    return res[0];\n}\n
climbing_stairs_backtrack.java
/* Backtracking */\nvoid backtrack(List<Integer> choices, int state, int n, List<Integer> res) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state == n)\n        res.set(0, res.get(0) + 1);\n    // Traverse all choices\n    for (Integer choice : choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n)\n            continue;\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nint climbingStairsBacktrack(int n) {\n    List<Integer> choices = Arrays.asList(1, 2); // Can choose to climb up 1 or 2 stairs\n    int state = 0; // Start climbing from the 0-th stair\n    List<Integer> res = new ArrayList<>();\n    res.add(0); // Use res[0] to record the solution count\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
climbing_stairs_backtrack.cs
/* Backtracking */\nvoid Backtrack(List<int> choices, int state, int n, List<int> res) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state == n)\n        res[0]++;\n    // Traverse all choices\n    foreach (int choice in choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n)\n            continue;\n        // Attempt: make choice, update state\n        Backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nint ClimbingStairsBacktrack(int n) {\n    List<int> choices = [1, 2]; // Can choose to climb up 1 or 2 stairs\n    int state = 0; // Start climbing from the 0-th stair\n    List<int> res = [0]; // Use res[0] to record the solution count\n    Backtrack(choices, state, n, res);\n    return res[0];\n}\n
climbing_stairs_backtrack.go
/* Backtracking */\nfunc backtrack(choices []int, state, n int, res []int) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if state == n {\n        res[0] = res[0] + 1\n    }\n    // Traverse all choices\n    for _, choice := range choices {\n        // Pruning: not allowed to go beyond the n-th stair\n        if state+choice > n {\n            continue\n        }\n        // Attempt: make choice, update state\n        backtrack(choices, state+choice, n, res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfunc climbingStairsBacktrack(n int) int {\n    // Can choose to climb up 1 or 2 stairs\n    choices := []int{1, 2}\n    // Start climbing from the 0-th stair\n    state := 0\n    res := make([]int, 1)\n    // Use res[0] to record the solution count\n    res[0] = 0\n    backtrack(choices, state, n, res)\n    return res[0]\n}\n
climbing_stairs_backtrack.swift
/* Backtracking */\nfunc backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if state == n {\n        res[0] += 1\n    }\n    // Traverse all choices\n    for choice in choices {\n        // Pruning: not allowed to go beyond the n-th stair\n        if state + choice > n {\n            continue\n        }\n        // Attempt: make choice, update state\n        backtrack(choices: choices, state: state + choice, n: n, res: &res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfunc climbingStairsBacktrack(n: Int) -> Int {\n    let choices = [1, 2] // Can choose to climb up 1 or 2 stairs\n    let state = 0 // Start climbing from the 0-th stair\n    var res: [Int] = []\n    res.append(0) // Use res[0] to record the solution count\n    backtrack(choices: choices, state: state, n: n, res: &res)\n    return res[0]\n}\n
climbing_stairs_backtrack.js
/* Backtracking */\nfunction backtrack(choices, state, n, res) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state === n) res.set(0, res.get(0) + 1);\n    // Traverse all choices\n    for (const choice of choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n) continue;\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfunction climbingStairsBacktrack(n) {\n    const choices = [1, 2]; // Can choose to climb up 1 or 2 stairs\n    const state = 0; // Start climbing from the 0-th stair\n    const res = new Map();\n    res.set(0, 0); // Use res[0] to record the solution count\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
climbing_stairs_backtrack.ts
/* Backtracking */\nfunction backtrack(\n    choices: number[],\n    state: number,\n    n: number,\n    res: Map<0, any>\n): void {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state === n) res.set(0, res.get(0) + 1);\n    // Traverse all choices\n    for (const choice of choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n) continue;\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfunction climbingStairsBacktrack(n: number): number {\n    const choices = [1, 2]; // Can choose to climb up 1 or 2 stairs\n    const state = 0; // Start climbing from the 0-th stair\n    const res = new Map();\n    res.set(0, 0); // Use res[0] to record the solution count\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
climbing_stairs_backtrack.dart
/* Backtracking */\nvoid backtrack(List<int> choices, int state, int n, List<int> res) {\n  // When climbing to the n-th stair, add 1 to the solution count\n  if (state == n) {\n    res[0]++;\n  }\n  // Traverse all choices\n  for (int choice in choices) {\n    // Pruning: not allowed to go beyond the n-th stair\n    if (state + choice > n) continue;\n    // Attempt: make choice, update state\n    backtrack(choices, state + choice, n, res);\n    // Backtrack\n  }\n}\n\n/* Climbing stairs: Backtracking */\nint climbingStairsBacktrack(int n) {\n  List<int> choices = [1, 2]; // Can choose to climb up 1 or 2 stairs\n  int state = 0; // Start climbing from the 0-th stair\n  List<int> res = [];\n  res.add(0); // Use res[0] to record the solution count\n  backtrack(choices, state, n, res);\n  return res[0];\n}\n
climbing_stairs_backtrack.rs
/* Backtracking */\nfn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if state == n {\n        res[0] = res[0] + 1;\n    }\n    // Traverse all choices\n    for &choice in choices {\n        // Pruning: not allowed to go beyond the n-th stair\n        if state + choice > n {\n            continue;\n        }\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfn climbing_stairs_backtrack(n: usize) -> i32 {\n    let choices = vec![1, 2]; // Can choose to climb up 1 or 2 stairs\n    let state = 0; // Start climbing from the 0-th stair\n    let mut res = Vec::new();\n    res.push(0); // Use res[0] to record the solution count\n    backtrack(&choices, state, n as i32, &mut res);\n    res[0]\n}\n
climbing_stairs_backtrack.c
/* Backtracking */\nvoid backtrack(int *choices, int state, int n, int *res, int len) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state == n)\n        res[0]++;\n    // Traverse all choices\n    for (int i = 0; i < len; i++) {\n        int choice = choices[i];\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n)\n            continue;\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res, len);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nint climbingStairsBacktrack(int n) {\n    int choices[2] = {1, 2}; // Can choose to climb up 1 or 2 stairs\n    int state = 0;           // Start climbing from the 0-th stair\n    int *res = (int *)malloc(sizeof(int));\n    *res = 0; // Use res[0] to record the solution count\n    int len = sizeof(choices) / sizeof(int);\n    backtrack(choices, state, n, res, len);\n    int result = *res;\n    free(res);\n    return result;\n}\n
climbing_stairs_backtrack.kt
/* Backtracking */\nfun backtrack(\n    choices: MutableList<Int>,\n    state: Int,\n    n: Int,\n    res: MutableList<Int>\n) {\n    // When climbing to the n-th stair, add 1 to the solution count\n    if (state == n)\n        res[0] = res[0] + 1\n    // Traverse all choices\n    for (choice in choices) {\n        // Pruning: not allowed to go beyond the n-th stair\n        if (state + choice > n) continue\n        // Attempt: make choice, update state\n        backtrack(choices, state + choice, n, res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: Backtracking */\nfun climbingStairsBacktrack(n: Int): Int {\n    val choices = mutableListOf(1, 2) // Can choose to climb up 1 or 2 stairs\n    val state = 0 // Start climbing from the 0-th stair\n    val res = mutableListOf<Int>()\n    res.add(0) // Use res[0] to record the solution count\n    backtrack(choices, state, n, res)\n    return res[0]\n}\n
climbing_stairs_backtrack.rb
### Backtracking ###\ndef backtrack(choices, state, n, res)\n  # When climbing to the n-th stair, add 1 to the solution count\n  res[0] += 1 if state == n\n  # Traverse all choices\n  for choice in choices\n    # Pruning: not allowed to go beyond the n-th stair\n    next if state + choice > n\n\n    # Attempt: make choice, update state\n    backtrack(choices, state + choice, n, res)\n  end\n  # Backtrack\nend\n\n### Climbing stairs: backtracking ###\ndef climbing_stairs_backtrack(n)\n  choices = [1, 2] # Can choose to climb up 1 or 2 stairs\n  state = 0 # Start climbing from the 0-th stair\n  res = [0] # Use res[0] to record the solution count\n  backtrack(choices, state, n, res)\n  res.first\nend\n
","path":["Chapter 14. Dynamic Programming","14.1   Introduction to Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1411-method-1-brute-force-search","level":2,"title":"14.1.1   Method 1: Brute Force Search","text":"

Backtracking algorithms typically do not explicitly decompose problems, but rather treat solving the problem as a series of decision steps, searching for all possible solutions through trial and pruning.

We can try to analyze this problem from the perspective of problem decomposition. Let the number of ways to climb to the \\(i\\)-th step be \\(dp[i]\\), then \\(dp[i]\\) is the original problem, and its subproblems include:

\\[ dp[i-1], dp[i-2], \\dots, dp[2], dp[1] \\]

Since we can only go up \\(1\\) or \\(2\\) steps in each round, when we stand on the \\(i\\)-th step, we could only have been on the \\(i-1\\)-th or \\(i-2\\)-th step in the previous round. In other words, we can only reach the \\(i\\)-th step from the \\(i-1\\)-th or \\(i-2\\)-th step.

This leads to an important conclusion: the number of ways to climb to the \\(i-1\\)-th step plus the number of ways to climb to the \\(i-2\\)-th step equals the number of ways to climb to the \\(i\\)-th step. The formula is as follows:

\\[ dp[i] = dp[i-1] + dp[i-2] \\]

This means that in the stair climbing problem, there exists a recurrence relation among the subproblems, the solution to the original problem can be constructed from the solutions to the subproblems. Figure 14-2 illustrates this recurrence relation.

Figure 14-2   Recurrence relation for the number of ways

We can obtain a brute force search solution based on the recurrence formula. Starting from \\(dp[n]\\), recursively decompose a larger problem into the sum of two smaller problems, until reaching the smallest subproblems \\(dp[1]\\) and \\(dp[2]\\) and returning. Among them, the solutions to the smallest subproblems are known, namely \\(dp[1] = 1\\) and \\(dp[2] = 2\\), representing \\(1\\) and \\(2\\) ways to climb to the \\(1\\)st and \\(2\\)nd steps, respectively.

Observe the following code, which, like standard backtracking code, belongs to depth-first search but is more concise:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dfs.py
def dfs(i: int) -> int:\n    \"\"\"Search\"\"\"\n    # Known dp[1] and dp[2], return them\n    if i == 1 or i == 2:\n        return i\n    # dp[i] = dp[i-1] + dp[i-2]\n    count = dfs(i - 1) + dfs(i - 2)\n    return count\n\ndef climbing_stairs_dfs(n: int) -> int:\n    \"\"\"Climbing stairs: Search\"\"\"\n    return dfs(n)\n
climbing_stairs_dfs.cpp
/* Search */\nint dfs(int i) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
climbing_stairs_dfs.java
/* Search */\nint dfs(int i) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
climbing_stairs_dfs.cs
/* Search */\nint DFS(int i) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = DFS(i - 1) + DFS(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nint ClimbingStairsDFS(int n) {\n    return DFS(n);\n}\n
climbing_stairs_dfs.go
/* Search */\nfunc dfs(i int) int {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    count := dfs(i-1) + dfs(i-2)\n    return count\n}\n\n/* Climbing stairs: Search */\nfunc climbingStairsDFS(n int) int {\n    return dfs(n)\n}\n
climbing_stairs_dfs.swift
/* Search */\nfunc dfs(i: Int) -> Int {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i: i - 1) + dfs(i: i - 2)\n    return count\n}\n\n/* Climbing stairs: Search */\nfunc climbingStairsDFS(n: Int) -> Int {\n    dfs(i: n)\n}\n
climbing_stairs_dfs.js
/* Search */\nfunction dfs(i) {\n    // Known dp[1] and dp[2], return them\n    if (i === 1 || i === 2) return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nfunction climbingStairsDFS(n) {\n    return dfs(n);\n}\n
climbing_stairs_dfs.ts
/* Search */\nfunction dfs(i: number): number {\n    // Known dp[1] and dp[2], return them\n    if (i === 1 || i === 2) return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nfunction climbingStairsDFS(n: number): number {\n    return dfs(n);\n}\n
climbing_stairs_dfs.dart
/* Search */\nint dfs(int i) {\n  // Known dp[1] and dp[2], return them\n  if (i == 1 || i == 2) return i;\n  // dp[i] = dp[i-1] + dp[i-2]\n  int count = dfs(i - 1) + dfs(i - 2);\n  return count;\n}\n\n/* Climbing stairs: Search */\nint climbingStairsDFS(int n) {\n  return dfs(n);\n}\n
climbing_stairs_dfs.rs
/* Search */\nfn dfs(i: usize) -> i32 {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i as i32;\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i - 1) + dfs(i - 2);\n    count\n}\n\n/* Climbing stairs: Search */\nfn climbing_stairs_dfs(n: usize) -> i32 {\n    dfs(n)\n}\n
climbing_stairs_dfs.c
/* Search */\nint dfs(int i) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: Search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
climbing_stairs_dfs.kt
/* Search */\nfun dfs(i: Int): Int {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2) return i\n    // dp[i] = dp[i-1] + dp[i-2]\n    val count = dfs(i - 1) + dfs(i - 2)\n    return count\n}\n\n/* Climbing stairs: Search */\nfun climbingStairsDFS(n: Int): Int {\n    return dfs(n)\n}\n
climbing_stairs_dfs.rb
### Search ###\ndef dfs(i)\n  # Known dp[1] and dp[2], return them\n  return i if i == 1 || i == 2\n  # dp[i] = dp[i-1] + dp[i-2]\n  dfs(i - 1) + dfs(i - 2)\nend\n\n### Climbing stairs: search ###\ndef climbing_stairs_dfs(n)\n  dfs(n)\nend\n

Figure 14-3 shows the recursion tree formed by brute force search. For the problem \\(dp[n]\\), the depth of its recursion tree is \\(n\\), with a time complexity of \\(O(2^n)\\). Exponential order represents explosive growth; if we input a relatively large \\(n\\), we will fall into a long wait.

Figure 14-3   Recursion tree for climbing stairs

Observing the above figure, the exponential time complexity is caused by \"overlapping subproblems\". For example, \\(dp[9]\\) is decomposed into \\(dp[8]\\) and \\(dp[7]\\), and \\(dp[8]\\) is decomposed into \\(dp[7]\\) and \\(dp[6]\\), both of which contain the subproblem \\(dp[7]\\).

And so on, subproblems contain smaller overlapping subproblems, ad infinitum. The vast majority of computational resources are wasted on these overlapping subproblems.

","path":["Chapter 14. Dynamic Programming","14.1   Introduction to Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1412-method-2-memoization","level":2,"title":"14.1.2   Method 2: Memoization","text":"

To improve algorithm efficiency, we want all overlapping subproblems to be computed only once. For this purpose, we declare an array mem to record the solution to each subproblem and prune overlapping subproblems during the search process.

  1. When computing \\(dp[i]\\) for the first time, we record it in mem[i] for later use.
  2. When we need to compute \\(dp[i]\\) again, we can directly retrieve the result from mem[i], thereby avoiding redundant computation of that subproblem.

The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dfs_mem.py
def dfs(i: int, mem: list[int]) -> int:\n    \"\"\"Memoization search\"\"\"\n    # Known dp[1] and dp[2], return them\n    if i == 1 or i == 2:\n        return i\n    # If record dp[i] exists, return it directly\n    if mem[i] != -1:\n        return mem[i]\n    # dp[i] = dp[i-1] + dp[i-2]\n    count = dfs(i - 1, mem) + dfs(i - 2, mem)\n    # Record dp[i]\n    mem[i] = count\n    return count\n\ndef climbing_stairs_dfs_mem(n: int) -> int:\n    \"\"\"Climbing stairs: Memoization search\"\"\"\n    # mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    mem = [-1] * (n + 1)\n    return dfs(n, mem)\n
climbing_stairs_dfs_mem.cpp
/* Memoization search */\nint dfs(int i, vector<int> &mem) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    vector<int> mem(n + 1, -1);\n    return dfs(n, mem);\n}\n
climbing_stairs_dfs_mem.java
/* Memoization search */\nint dfs(int i, int[] mem) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    int[] mem = new int[n + 1];\n    Arrays.fill(mem, -1);\n    return dfs(n, mem);\n}\n
climbing_stairs_dfs_mem.cs
/* Memoization search */\nint DFS(int i, int[] mem) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = DFS(i - 1, mem) + DFS(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nint ClimbingStairsDFSMem(int n) {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    int[] mem = new int[n + 1];\n    Array.Fill(mem, -1);\n    return DFS(n, mem);\n}\n
climbing_stairs_dfs_mem.go
/* Memoization search */\nfunc dfsMem(i int, mem []int) int {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // If record dp[i] exists, return it directly\n    if mem[i] != -1 {\n        return mem[i]\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    count := dfsMem(i-1, mem) + dfsMem(i-2, mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: Memoization search */\nfunc climbingStairsDFSMem(n int) int {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    mem := make([]int, n+1)\n    for i := range mem {\n        mem[i] = -1\n    }\n    return dfsMem(n, mem)\n}\n
climbing_stairs_dfs_mem.swift
/* Memoization search */\nfunc dfs(i: Int, mem: inout [Int]) -> Int {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // If record dp[i] exists, return it directly\n    if mem[i] != -1 {\n        return mem[i]\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: Memoization search */\nfunc climbingStairsDFSMem(n: Int) -> Int {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    var mem = Array(repeating: -1, count: n + 1)\n    return dfs(i: n, mem: &mem)\n}\n
climbing_stairs_dfs_mem.js
/* Memoization search */\nfunction dfs(i, mem) {\n    // Known dp[1] and dp[2], return them\n    if (i === 1 || i === 2) return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1) return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nfunction climbingStairsDFSMem(n) {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    const mem = new Array(n + 1).fill(-1);\n    return dfs(n, mem);\n}\n
climbing_stairs_dfs_mem.ts
/* Memoization search */\nfunction dfs(i: number, mem: number[]): number {\n    // Known dp[1] and dp[2], return them\n    if (i === 1 || i === 2) return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1) return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nfunction climbingStairsDFSMem(n: number): number {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    const mem = new Array(n + 1).fill(-1);\n    return dfs(n, mem);\n}\n
climbing_stairs_dfs_mem.dart
/* Memoization search */\nint dfs(int i, List<int> mem) {\n  // Known dp[1] and dp[2], return them\n  if (i == 1 || i == 2) return i;\n  // If record dp[i] exists, return it directly\n  if (mem[i] != -1) return mem[i];\n  // dp[i] = dp[i-1] + dp[i-2]\n  int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n  // Record dp[i]\n  mem[i] = count;\n  return count;\n}\n\n/* Climbing stairs: Memoization search */\nint climbingStairsDFSMem(int n) {\n  // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n  List<int> mem = List.filled(n + 1, -1);\n  return dfs(n, mem);\n}\n
climbing_stairs_dfs_mem.rs
/* Memoization search */\nfn dfs(i: usize, mem: &mut [i32]) -> i32 {\n    // Known dp[1] and dp[2], return them\n    if i == 1 || i == 2 {\n        return i as i32;\n    }\n    // If record dp[i] exists, return it directly\n    if mem[i] != -1 {\n        return mem[i];\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    count\n}\n\n/* Climbing stairs: Memoization search */\nfn climbing_stairs_dfs_mem(n: usize) -> i32 {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    let mut mem = vec![-1; n + 1];\n    dfs(n, &mut mem)\n}\n
climbing_stairs_dfs_mem.c
/* Memoization search */\nint dfs(int i, int *mem) {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2)\n        return i;\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: Memoization search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    int *mem = (int *)malloc((n + 1) * sizeof(int));\n    for (int i = 0; i <= n; i++) {\n        mem[i] = -1;\n    }\n    int result = dfs(n, mem);\n    free(mem);\n    return result;\n}\n
climbing_stairs_dfs_mem.kt
/* Memoization search */\nfun dfs(i: Int, mem: IntArray): Int {\n    // Known dp[1] and dp[2], return them\n    if (i == 1 || i == 2) return i\n    // If record dp[i] exists, return it directly\n    if (mem[i] != -1) return mem[i]\n    // dp[i] = dp[i-1] + dp[i-2]\n    val count = dfs(i - 1, mem) + dfs(i - 2, mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: Memoization search */\nfun climbingStairsDFSMem(n: Int): Int {\n    // mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n    val mem = IntArray(n + 1)\n    mem.fill(-1)\n    return dfs(n, mem)\n}\n
climbing_stairs_dfs_mem.rb
### Memoization search ###\ndef dfs(i, mem)\n  # Known dp[1] and dp[2], return them\n  return i if i == 1 || i == 2\n  # If record dp[i] exists, return it directly\n  return mem[i] if mem[i] != -1\n\n  # dp[i] = dp[i-1] + dp[i-2]\n  count = dfs(i - 1, mem) + dfs(i - 2, mem)\n  # Record dp[i]\n  mem[i] = count\nend\n\n### Climbing stairs: memoization search ###\ndef climbing_stairs_dfs_mem(n)\n  # mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record\n  mem = Array.new(n + 1, -1)\n  dfs(n, mem)\nend\n

Observe Figure 14-4, after memoization, all overlapping subproblems only need to be computed once, optimizing the time complexity to \\(O(n)\\), which is a tremendous leap.

Figure 14-4   Recursion tree with memoization

","path":["Chapter 14. Dynamic Programming","14.1   Introduction to Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1413-method-3-dynamic-programming","level":2,"title":"14.1.3   Method 3: Dynamic Programming","text":"

Memoization is a \"top-down\" method: we start from the original problem (root node), recursively decompose larger subproblems into smaller ones, until reaching the smallest known subproblems (leaf nodes). Afterward, by backtracking, we collect the solutions to the subproblems layer by layer to construct the solution to the original problem.

In contrast, dynamic programming is a \"bottom-up\" method: starting from the solutions to the smallest subproblems, iteratively constructing solutions to larger subproblems until obtaining the solution to the original problem.

Since dynamic programming does not include a backtracking process, it only requires loop iteration for implementation and does not need recursion. In the following code, we initialize an array dp to store the solutions to subproblems, which serves the same recording function as the array mem in memoization:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dp.py
def climbing_stairs_dp(n: int) -> int:\n    \"\"\"Climbing stairs: Dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return n\n    # Initialize dp table, used to store solutions to subproblems\n    dp = [0] * (n + 1)\n    # Initial state: preset the solution to the smallest subproblem\n    dp[1], dp[2] = 1, 2\n    # State transition: gradually solve larger subproblems from smaller ones\n    for i in range(3, n + 1):\n        dp[i] = dp[i - 1] + dp[i - 2]\n    return dp[n]\n
climbing_stairs_dp.cpp
/* Climbing stairs: Dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize dp table, used to store solutions to subproblems\n    vector<int> dp(n + 1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
climbing_stairs_dp.java
/* Climbing stairs: Dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize dp table, used to store solutions to subproblems\n    int[] dp = new int[n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
climbing_stairs_dp.cs
/* Climbing stairs: Dynamic programming */\nint ClimbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize dp table, used to store solutions to subproblems\n    int[] dp = new int[n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
climbing_stairs_dp.go
/* Climbing stairs: Dynamic programming */\nfunc climbingStairsDP(n int) int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    dp := make([]int, n+1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1\n    dp[2] = 2\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i] = dp[i-1] + dp[i-2]\n    }\n    return dp[n]\n}\n
climbing_stairs_dp.swift
/* Climbing stairs: Dynamic programming */\nfunc climbingStairsDP(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    var dp = Array(repeating: 0, count: n + 1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1\n    dp[2] = 2\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3 ... n {\n        dp[i] = dp[i - 1] + dp[i - 2]\n    }\n    return dp[n]\n}\n
climbing_stairs_dp.js
/* Climbing stairs: Dynamic programming */\nfunction climbingStairsDP(n) {\n    if (n === 1 || n === 2) return n;\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = new Array(n + 1).fill(-1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
climbing_stairs_dp.ts
/* Climbing stairs: Dynamic programming */\nfunction climbingStairsDP(n: number): number {\n    if (n === 1 || n === 2) return n;\n    // Initialize dp table, used to store solutions to subproblems\n    const dp = new Array(n + 1).fill(-1);\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
climbing_stairs_dp.dart
/* Climbing stairs: Dynamic programming */\nint climbingStairsDP(int n) {\n  if (n == 1 || n == 2) return n;\n  // Initialize dp table, used to store solutions to subproblems\n  List<int> dp = List.filled(n + 1, 0);\n  // Initial state: preset the solution to the smallest subproblem\n  dp[1] = 1;\n  dp[2] = 2;\n  // State transition: gradually solve larger subproblems from smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i] = dp[i - 1] + dp[i - 2];\n  }\n  return dp[n];\n}\n
climbing_stairs_dp.rs
/* Climbing stairs: Dynamic programming */\nfn climbing_stairs_dp(n: usize) -> i32 {\n    // Known dp[1] and dp[2], return them\n    if n == 1 || n == 2 {\n        return n as i32;\n    }\n    // Initialize dp table, used to store solutions to subproblems\n    let mut dp = vec![-1; n + 1];\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i in 3..=n {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    dp[n]\n}\n
climbing_stairs_dp.c
/* Climbing stairs: Dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize dp table, used to store solutions to subproblems\n    int *dp = (int *)malloc((n + 1) * sizeof(int));\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    int result = dp[n];\n    free(dp);\n    return result;\n}\n
climbing_stairs_dp.kt
/* Climbing stairs: Dynamic programming */\nfun climbingStairsDP(n: Int): Int {\n    if (n == 1 || n == 2) return n\n    // Initialize dp table, used to store solutions to subproblems\n    val dp = IntArray(n + 1)\n    // Initial state: preset the solution to the smallest subproblem\n    dp[1] = 1\n    dp[2] = 2\n    // State transition: gradually solve larger subproblems from smaller ones\n    for (i in 3..n) {\n        dp[i] = dp[i - 1] + dp[i - 2]\n    }\n    return dp[n]\n}\n
climbing_stairs_dp.rb
### Climbing stairs: dynamic programming ###\ndef climbing_stairs_dp(n)\n  return n  if n == 1 || n == 2\n\n  # Initialize dp table, used to store solutions to subproblems\n  dp = Array.new(n + 1, 0)\n  # Initial state: preset the solution to the smallest subproblem\n  dp[1], dp[2] = 1, 2\n  # State transition: gradually solve larger subproblems from smaller ones\n  (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] }\n\n  dp[n]\nend\n

Figure 14-5 simulates the execution process of the above code.

Figure 14-5   Dynamic programming process for climbing stairs

Like backtracking algorithms, dynamic programming also uses the \"state\" concept to represent specific stages of problem solving, with each state corresponding to a subproblem and its corresponding local optimal solution. For example, the state in the stair climbing problem is defined as the current stair step number \\(i\\).

Based on the above content, we can summarize the commonly used terminology in dynamic programming.

  • The array dp is called the dp table, where \\(dp[i]\\) represents the solution to the subproblem corresponding to state \\(i\\).
  • The states corresponding to the smallest subproblems (the \\(1\\)st and \\(2\\)nd steps) are called initial states.
  • The recurrence formula \\(dp[i] = dp[i-1] + dp[i-2]\\) is called the state transition equation.
","path":["Chapter 14. Dynamic Programming","14.1   Introduction to Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1414-space-optimization","level":2,"title":"14.1.4   Space Optimization","text":"

Observant readers may have noticed that since \\(dp[i]\\) is only related to \\(dp[i-1]\\) and \\(dp[i-2]\\), we do not need to use an array dp to store the solutions to all subproblems, but can simply use two variables to roll forward. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dp.py
def climbing_stairs_dp_comp(n: int) -> int:\n    \"\"\"Climbing stairs: Space-optimized dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return n\n    a, b = 1, 2\n    for _ in range(3, n + 1):\n        a, b = b, a + b\n    return b\n
climbing_stairs_dp.cpp
/* Climbing stairs: Space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.java
/* Climbing stairs: Space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.cs
/* Climbing stairs: Space-optimized dynamic programming */\nint ClimbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.go
/* Climbing stairs: Space-optimized dynamic programming */\nfunc climbingStairsDPComp(n int) int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    a, b := 1, 2\n    // State transition: gradually solve larger subproblems from smaller ones\n    for i := 3; i <= n; i++ {\n        a, b = b, a+b\n    }\n    return b\n}\n
climbing_stairs_dp.swift
/* Climbing stairs: Space-optimized dynamic programming */\nfunc climbingStairsDPComp(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    var a = 1\n    var b = 2\n    for _ in 3 ... n {\n        (a, b) = (b, a + b)\n    }\n    return b\n}\n
climbing_stairs_dp.js
/* Climbing stairs: Space-optimized dynamic programming */\nfunction climbingStairsDPComp(n) {\n    if (n === 1 || n === 2) return n;\n    let a = 1,\n        b = 2;\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.ts
/* Climbing stairs: Space-optimized dynamic programming */\nfunction climbingStairsDPComp(n: number): number {\n    if (n === 1 || n === 2) return n;\n    let a = 1,\n        b = 2;\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.dart
/* Climbing stairs: Space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n  if (n == 1 || n == 2) return n;\n  int a = 1, b = 2;\n  for (int i = 3; i <= n; i++) {\n    int tmp = b;\n    b = a + b;\n    a = tmp;\n  }\n  return b;\n}\n
climbing_stairs_dp.rs
/* Climbing stairs: Space-optimized dynamic programming */\nfn climbing_stairs_dp_comp(n: usize) -> i32 {\n    if n == 1 || n == 2 {\n        return n as i32;\n    }\n    let (mut a, mut b) = (1, 2);\n    for _ in 3..=n {\n        let tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    b\n}\n
climbing_stairs_dp.c
/* Climbing stairs: Space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
climbing_stairs_dp.kt
/* Climbing stairs: Space-optimized dynamic programming */\nfun climbingStairsDPComp(n: Int): Int {\n    if (n == 1 || n == 2) return n\n    var a = 1\n    var b = 2\n    for (i in 3..n) {\n        val temp = b\n        b += a\n        a = temp\n    }\n    return b\n}\n
climbing_stairs_dp.rb
### Climbing stairs: space-optimized DP ###\ndef climbing_stairs_dp_comp(n)\n  return n if n == 1 || n == 2\n\n  a, b = 1, 2\n  (3...(n + 1)).each { a, b = b, a + b }\n\n  b\nend\n

Observing the above code, since the space occupied by the array dp is saved, the space complexity is reduced from \\(O(n)\\) to \\(O(1)\\).

In dynamic programming problems, the current state often depends only on a limited number of preceding states, allowing us to retain only the necessary states and save memory space through \"dimension reduction\". This space optimization technique is called \"rolling variable\" or \"rolling array\".

","path":["Chapter 14. Dynamic Programming","14.1   Introduction to Dynamic Programming"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/","level":1,"title":"14.4   0-1 Knapsack Problem","text":"

The knapsack problem is an excellent introductory problem for dynamic programming and is one of the most common problem forms in dynamic programming. It has many variants, such as the 0-1 knapsack problem, the unbounded knapsack problem, and the multiple knapsack problem.

In this section, we will first solve the most common 0-1 knapsack problem.

Question

Given \\(n\\) items, where the weight of the \\(i\\)-th item is \\(wgt[i-1]\\) and its value is \\(val[i-1]\\), and a knapsack with capacity \\(cap\\). Each item can only be selected once. What is the maximum value that can be placed in the knapsack within the capacity limit?

Observe Figure 14-17. Since item number \\(i\\) starts counting from \\(1\\) and array indices start from \\(0\\), item \\(i\\) corresponds to weight \\(wgt[i-1]\\) and value \\(val[i-1]\\).

Figure 14-17   Example data for 0-1 knapsack

We can view the 0-1 knapsack problem as a process consisting of \\(n\\) rounds of decisions, where for each item there are two decisions: not putting it in and putting it in, thus the problem satisfies the decision tree model.

The goal of this problem is to find \"the maximum value that can be placed in the knapsack within the capacity limit\", so it is more likely to be a dynamic programming problem.

Step 1: Think about the decisions in each round, define the state, and thus obtain the \\(dp\\) table

For each item, if not placed in the knapsack, the knapsack capacity remains unchanged; if placed in, the knapsack capacity decreases. From this, we can derive the state definition: current item number \\(i\\) and knapsack capacity \\(c\\), denoted as \\([i, c]\\).

State \\([i, c]\\) corresponds to the subproblem: the maximum value among the first \\(i\\) items in a knapsack of capacity \\(c\\), denoted as \\(dp[i, c]\\).

What we need to find is \\(dp[n, cap]\\), so we need a two-dimensional \\(dp\\) table of size \\((n+1) \\times (cap+1)\\).

Step 2: Identify the optimal substructure, and then derive the state transition equation

After making the decision for item \\(i\\), what remains is the subproblem of the first \\(i-1\\) items, which can be divided into the following two cases.

  • Not putting item \\(i\\): The knapsack capacity remains unchanged, and the state changes to \\([i-1, c]\\).
  • Putting item \\(i\\): The knapsack capacity decreases by \\(wgt[i-1]\\), the value increases by \\(val[i-1]\\), and the state changes to \\([i-1, c-wgt[i-1]]\\).

The above analysis reveals the optimal substructure of this problem: the maximum value \\(dp[i, c]\\) equals the larger value between not putting item \\(i\\) and putting item \\(i\\). From this, the state transition equation can be derived:

\\[ dp[i, c] = \\max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) \\]

Note that if the weight of the current item \\(wgt[i - 1]\\) exceeds the remaining knapsack capacity \\(c\\), then the only option is not to put it in the knapsack.

Step 3: Determine boundary conditions and state transition order

When there are no items or the knapsack capacity is \\(0\\), the maximum value is \\(0\\), i.e., the first column \\(dp[i, 0]\\) and the first row \\(dp[0, c]\\) are both equal to \\(0\\).

The current state \\([i, c]\\) is transferred from the state above \\([i-1, c]\\) and the state in the upper-left \\([i-1, c-wgt[i-1]]\\), so the entire \\(dp\\) table is traversed in order through two nested loops.

Based on the above analysis, we will next implement the brute force search, memoization, and dynamic programming solutions in order.

","path":["Chapter 14. Dynamic Programming","14.4   0-1 Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#1-method-1-brute-force-search","level":3,"title":"1.   Method 1: Brute Force Search","text":"

The search code includes the following elements.

  • Recursive parameters: state \\([i, c]\\).
  • Return value: solution to the subproblem \\(dp[i, c]\\).
  • Termination condition: when the item number is out of bounds \\(i = 0\\) or the remaining knapsack capacity is \\(0\\), terminate recursion and return value \\(0\\).
  • Pruning: if the weight of the current item exceeds the remaining knapsack capacity, only the option of not putting it in is available.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int:\n    \"\"\"0-1 knapsack: Brute-force search\"\"\"\n    # If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 or c == 0:\n        return 0\n    # If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c:\n        return knapsack_dfs(wgt, val, i - 1, c)\n    # Calculate the maximum value of not putting in and putting in item i\n    no = knapsack_dfs(wgt, val, i - 1, c)\n    yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]\n    # Return the larger value of the two options\n    return max(no, yes)\n
knapsack.cpp
/* 0-1 knapsack: Brute-force search */\nint knapsackDFS(vector<int> &wgt, vector<int> &val, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return max(no, yes);\n}\n
knapsack.java
/* 0-1 knapsack: Brute-force search */\nint knapsackDFS(int[] wgt, int[] val, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return Math.max(no, yes);\n}\n
knapsack.cs
/* 0-1 knapsack: Brute-force search */\nint KnapsackDFS(int[] weight, int[] val, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (weight[i - 1] > c) {\n        return KnapsackDFS(weight, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = KnapsackDFS(weight, val, i - 1, c);\n    int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return Math.Max(no, yes);\n}\n
knapsack.go
/* 0-1 knapsack: Brute-force search */\nfunc knapsackDFS(wgt, val []int, i, c int) int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i-1] > c {\n        return knapsackDFS(wgt, val, i-1, c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    no := knapsackDFS(wgt, val, i-1, c)\n    yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1]\n    // Return the larger value of the two options\n    return int(math.Max(float64(no), float64(yes)))\n}\n
knapsack.swift
/* 0-1 knapsack: Brute-force search */\nfunc knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c {\n        return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c)\n    let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1]\n    // Return the larger value of the two options\n    return max(no, yes)\n}\n
knapsack.js
/* 0-1 knapsack: Brute-force search */\nfunction knapsackDFS(wgt, val, i, c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    const no = knapsackDFS(wgt, val, i - 1, c);\n    const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return Math.max(no, yes);\n}\n
knapsack.ts
/* 0-1 knapsack: Brute-force search */\nfunction knapsackDFS(\n    wgt: Array<number>,\n    val: Array<number>,\n    i: number,\n    c: number\n): number {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    const no = knapsackDFS(wgt, val, i - 1, c);\n    const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return Math.max(no, yes);\n}\n
knapsack.dart
/* 0-1 knapsack: Brute-force search */\nint knapsackDFS(List<int> wgt, List<int> val, int i, int c) {\n  // If all items have been selected or knapsack has no remaining capacity, return value 0\n  if (i == 0 || c == 0) {\n    return 0;\n  }\n  // If exceeds knapsack capacity, can only choose not to put it in\n  if (wgt[i - 1] > c) {\n    return knapsackDFS(wgt, val, i - 1, c);\n  }\n  // Calculate the maximum value of not putting in and putting in item i\n  int no = knapsackDFS(wgt, val, i - 1, c);\n  int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n  // Return the larger value of the two options\n  return max(no, yes);\n}\n
knapsack.rs
/* 0-1 knapsack: Brute-force search */\nfn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c as i32 {\n        return knapsack_dfs(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    let no = knapsack_dfs(wgt, val, i - 1, c);\n    let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1];\n    // Return the larger value of the two options\n    std::cmp::max(no, yes)\n}\n
knapsack.c
/* 0-1 knapsack: Brute-force search */\nint knapsackDFS(int wgt[], int val[], int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value of the two options\n    return myMax(no, yes);\n}\n
knapsack.kt
/* 0-1 knapsack: Brute-force search */\nfun knapsackDFS(\n    wgt: IntArray,\n    _val: IntArray,\n    i: Int,\n    c: Int\n): Int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, _val, i - 1, c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    val no = knapsackDFS(wgt, _val, i - 1, c)\n    val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1]\n    // Return the larger value of the two options\n    return max(no, yes)\n}\n
knapsack.rb
### 0-1 knapsack: brute force search ###\ndef knapsack_dfs(wgt, val, i, c)\n  # If all items have been selected or knapsack has no remaining capacity, return value 0\n  return 0 if i == 0 || c == 0\n  # If exceeds knapsack capacity, can only choose not to put it in\n  return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c\n  # Calculate the maximum value of not putting in and putting in item i\n  no = knapsack_dfs(wgt, val, i - 1, c)\n  yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]\n  # Return the larger value of the two options\n  [no, yes].max\nend\n

As shown in Figure 14-18, since each item generates two search branches of not selecting and selecting, the time complexity is \\(O(2^n)\\).

Observing the recursion tree, it is easy to see overlapping subproblems, such as \\(dp[1, 10]\\). When there are many items, large knapsack capacity, and especially many items with the same weight, the number of overlapping subproblems will increase significantly.

Figure 14-18   Brute force search recursion tree for 0-1 knapsack problem

","path":["Chapter 14. Dynamic Programming","14.4   0-1 Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#2-method-2-memoization","level":3,"title":"2.   Method 2: Memoization","text":"

To ensure that overlapping subproblems are only computed once, we use a memo list mem to record the solutions to subproblems, where mem[i][c] corresponds to \\(dp[i, c]\\).

After introducing memoization, the time complexity depends on the number of subproblems, which is \\(O(n \\times cap)\\). The implementation code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
def knapsack_dfs_mem(\n    wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int\n) -> int:\n    \"\"\"0-1 knapsack: Memoization search\"\"\"\n    # If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 or c == 0:\n        return 0\n    # If there's a record, return it directly\n    if mem[i][c] != -1:\n        return mem[i][c]\n    # If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c:\n        return knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n    # Calculate the maximum value of not putting in and putting in item i\n    no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n    yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]\n    # Record and return the larger value of the two options\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n
knapsack.cpp
/* 0-1 knapsack: Memoization search */\nint knapsackDFSMem(vector<int> &wgt, vector<int> &val, vector<vector<int>> &mem, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = max(no, yes);\n    return mem[i][c];\n}\n
knapsack.java
/* 0-1 knapsack: Memoization search */\nint knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
knapsack.cs
/* 0-1 knapsack: Memoization search */\nint KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (weight[i - 1] > c) {\n        return KnapsackDFSMem(weight, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = KnapsackDFSMem(weight, val, mem, i - 1, c);\n    int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = Math.Max(no, yes);\n    return mem[i][c];\n}\n
knapsack.go
/* 0-1 knapsack: Memoization search */\nfunc knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If there's a record, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c]\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i-1] > c {\n        return knapsackDFSMem(wgt, val, mem, i-1, c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    no := knapsackDFSMem(wgt, val, mem, i-1, c)\n    yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1]\n    // Return the larger value of the two options\n    mem[i][c] = int(math.Max(float64(no), float64(yes)))\n    return mem[i][c]\n}\n
knapsack.swift
/* 0-1 knapsack: Memoization search */\nfunc knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If there's a record, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c]\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c {\n        return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c)\n    let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1]\n    // Record and return the larger value of the two options\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n}\n
knapsack.js
/* 0-1 knapsack: Memoization search */\nfunction knapsackDFSMem(wgt, val, mem, i, c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] !== -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    const no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    const yes =\n        knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
knapsack.ts
/* 0-1 knapsack: Memoization search */\nfunction knapsackDFSMem(\n    wgt: Array<number>,\n    val: Array<number>,\n    mem: Array<Array<number>>,\n    i: number,\n    c: number\n): number {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] !== -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    const no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    const yes =\n        knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
knapsack.dart
/* 0-1 knapsack: Memoization search */\nint knapsackDFSMem(\n  List<int> wgt,\n  List<int> val,\n  List<List<int>> mem,\n  int i,\n  int c,\n) {\n  // If all items have been selected or knapsack has no remaining capacity, return value 0\n  if (i == 0 || c == 0) {\n    return 0;\n  }\n  // If there's a record, return it directly\n  if (mem[i][c] != -1) {\n    return mem[i][c];\n  }\n  // If exceeds knapsack capacity, can only choose not to put it in\n  if (wgt[i - 1] > c) {\n    return knapsackDFSMem(wgt, val, mem, i - 1, c);\n  }\n  // Calculate the maximum value of not putting in and putting in item i\n  int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n  int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n  // Record and return the larger value of the two options\n  mem[i][c] = max(no, yes);\n  return mem[i][c];\n}\n
knapsack.rs
/* 0-1 knapsack: Memoization search */\nfn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec<Vec<i32>>, i: usize, c: usize) -> i32 {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if i == 0 || c == 0 {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if wgt[i - 1] > c as i32 {\n        return knapsack_dfs_mem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c);\n    let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = std::cmp::max(no, yes);\n    mem[i][c]\n}\n
knapsack.c
/* 0-1 knapsack: Memoization search */\nint knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value of the two options\n    mem[i][c] = myMax(no, yes);\n    return mem[i][c];\n}\n
knapsack.kt
/* 0-1 knapsack: Memoization search */\nfun knapsackDFSMem(\n    wgt: IntArray,\n    _val: IntArray,\n    mem: Array<IntArray>,\n    i: Int,\n    c: Int\n): Int {\n    // If all items have been selected or knapsack has no remaining capacity, return value 0\n    if (i == 0 || c == 0) {\n        return 0\n    }\n    // If there's a record, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c]\n    }\n    // If exceeds knapsack capacity, can only choose not to put it in\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, _val, mem, i - 1, c)\n    }\n    // Calculate the maximum value of not putting in and putting in item i\n    val no = knapsackDFSMem(wgt, _val, mem, i - 1, c)\n    val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1]\n    // Record and return the larger value of the two options\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n}\n
knapsack.rb
### 0-1 knapsack: memoization search ###\ndef knapsack_dfs_mem(wgt, val, mem, i, c)\n  # If all items have been selected or knapsack has no remaining capacity, return value 0\n  return 0 if i == 0 || c == 0\n  # If there's a record, return it directly\n  return mem[i][c] if mem[i][c] != -1\n  # If exceeds knapsack capacity, can only choose not to put it in\n  return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c\n  # Calculate the maximum value of not putting in and putting in item i\n  no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n  yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]\n  # Record and return the larger value of the two options\n  mem[i][c] = [no, yes].max\nend\n

Figure 14-19 shows the search branches pruned in memoization.

Figure 14-19   Memoization recursion tree for 0-1 knapsack problem

","path":["Chapter 14. Dynamic Programming","14.4   0-1 Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#3-method-3-dynamic-programming","level":3,"title":"3.   Method 3: Dynamic Programming","text":"

Dynamic programming is essentially the process of filling the \\(dp\\) table during state transitions. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"0-1 knapsack: Dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize dp table\n    dp = [[0] * (cap + 1) for _ in range(n + 1)]\n    # State transition\n    for i in range(1, n + 1):\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            else:\n                # The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])\n    return dp[n][cap]\n
knapsack.cpp
/* 0-1 knapsack: Dynamic programming */\nint knapsackDP(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize dp table\n    vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0));\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
knapsack.java
/* 0-1 knapsack: Dynamic programming */\nint knapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize dp table\n    int[][] dp = new int[n + 1][cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
knapsack.cs
/* 0-1 knapsack: Dynamic programming */\nint KnapsackDP(int[] weight, int[] val, int cap) {\n    int n = weight.Length;\n    // Initialize dp table\n    int[,] dp = new int[n + 1, cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (weight[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i, c] = dp[i - 1, c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]);\n            }\n        }\n    }\n    return dp[n, cap];\n}\n
knapsack.go
/* 0-1 knapsack: Dynamic programming */\nfunc knapsackDP(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, cap+1)\n    }\n    // State transition\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i-1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
knapsack.swift
/* 0-1 knapsack: Dynamic programming */\nfunc knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize dp table\n    var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1)\n    // State transition\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
knapsack.js
/* 0-1 knapsack: Dynamic programming */\nfunction knapsackDP(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array(n + 1)\n        .fill(0)\n        .map(() => Array(cap + 1).fill(0));\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
knapsack.ts
/* 0-1 knapsack: Dynamic programming */\nfunction knapsackDP(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
knapsack.dart
/* 0-1 knapsack: Dynamic programming */\nint knapsackDP(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0));\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If exceeds knapsack capacity, don't select item i\n        dp[i][c] = dp[i - 1][c];\n      } else {\n        // The larger value between not selecting and selecting item i\n        dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[n][cap];\n}\n
knapsack.rs
/* 0-1 knapsack: Dynamic programming */\nfn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize dp table\n    let mut dp = vec![vec![0; cap + 1]; n + 1];\n    // State transition\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = std::cmp::max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1],\n                );\n            }\n        }\n    }\n    dp[n][cap]\n}\n
knapsack.c
/* 0-1 knapsack: Dynamic programming */\nint knapsackDP(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(cap + 1, sizeof(int));\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[n][cap];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
knapsack.kt
/* 0-1 knapsack: Dynamic programming */\nfun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize dp table\n    val dp = Array(n + 1) { IntArray(cap + 1) }\n    // State transition\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
knapsack.rb
### 0-1 knapsack: dynamic programming ###\ndef knapsack_dp(wgt, val, cap)\n  n = wgt.length\n  # Initialize dp table\n  dp = Array.new(n + 1) { Array.new(cap + 1, 0) }\n  # State transition\n  for i in 1...(n + 1)\n    for c in 1...(cap + 1)\n      if wgt[i - 1] > c\n        # If exceeds knapsack capacity, don't select item i\n        dp[i][c] = dp[i - 1][c]\n      else\n        # The larger value between not selecting and selecting item i\n        dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[n][cap]\nend\n

As shown in Figure 14-20, both time complexity and space complexity are determined by the size of the array dp, which is \\(O(n \\times cap)\\).

<1><2><3><4><5><6><7><8><9><10><11><12><13><14>

Figure 14-20   Dynamic programming process for 0-1 knapsack problem

","path":["Chapter 14. Dynamic Programming","14.4   0-1 Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#4-space-optimization","level":3,"title":"4.   Space Optimization","text":"

Since each state is only related to the state in the row above it, we can use two arrays rolling forward to reduce the space complexity from \\(O(n^2)\\) to \\(O(n)\\).

Further thinking, can we achieve space optimization using just one array? Observing, we can see that each state is transferred from the cell directly above or the cell in the upper-left. If there is only one array, when we start traversing row \\(i\\), that array still stores the state of row \\(i-1\\).

  • If using forward traversal, then when traversing to \\(dp[i, j]\\), the values in the upper-left \\(dp[i-1, 1]\\) ~ \\(dp[i-1, j-1]\\) may have already been overwritten, thus preventing correct state transition.
  • If using reverse traversal, there will be no overwriting issue, and state transition can proceed correctly.

Figure 14-21 shows the transition process from row \\(i = 1\\) to row \\(i = 2\\) using a single array. Please consider the difference between forward and reverse traversal.

<1><2><3><4><5><6>

Figure 14-21   Space-optimized dynamic programming process for 0-1 knapsack

In the code implementation, we simply need to delete the first dimension \\(i\\) of the array dp and change the inner loop to reverse traversal:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"0-1 knapsack: Space-optimized dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize dp table\n    dp = [0] * (cap + 1)\n    # State transition\n    for i in range(1, n + 1):\n        # Traverse in reverse order\n        for c in range(cap, 0, -1):\n            if wgt[i - 1] > c:\n                # If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c]\n            else:\n                # The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n    return dp[cap]\n
knapsack.cpp
/* 0-1 knapsack: Space-optimized dynamic programming */\nint knapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize dp table\n    vector<int> dp(cap + 1, 0);\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
knapsack.java
/* 0-1 knapsack: Space-optimized dynamic programming */\nint knapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize dp table\n    int[] dp = new int[cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
knapsack.cs
/* 0-1 knapsack: Space-optimized dynamic programming */\nint KnapsackDPComp(int[] weight, int[] val, int cap) {\n    int n = weight.Length;\n    // Initialize dp table\n    int[] dp = new int[cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (int c = cap; c > 0; c--) {\n            if (weight[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
knapsack.go
/* 0-1 knapsack: Space-optimized dynamic programming */\nfunc knapsackDPComp(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize dp table\n    dp := make([]int, cap+1)\n    // State transition\n    for i := 1; i <= n; i++ {\n        // Traverse in reverse order\n        for c := cap; c >= 1; c-- {\n            if wgt[i-1] <= c {\n                // The larger value between not selecting and selecting item i\n                dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[cap]\n}\n
knapsack.swift
/* 0-1 knapsack: Space-optimized dynamic programming */\nfunc knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize dp table\n    var dp = Array(repeating: 0, count: cap + 1)\n    // State transition\n    for i in 1 ... n {\n        // Traverse in reverse order\n        for c in (1 ... cap).reversed() {\n            if wgt[i - 1] <= c {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
knapsack.js
/* 0-1 knapsack: Space-optimized dynamic programming */\nfunction knapsackDPComp(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array(cap + 1).fill(0);\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (let c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
knapsack.ts
/* 0-1 knapsack: Space-optimized dynamic programming */\nfunction knapsackDPComp(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array(cap + 1).fill(0);\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (let c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
knapsack.dart
/* 0-1 knapsack: Space-optimized dynamic programming */\nint knapsackDPComp(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize dp table\n  List<int> dp = List.filled(cap + 1, 0);\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    // Traverse in reverse order\n    for (int c = cap; c >= 1; c--) {\n      if (wgt[i - 1] <= c) {\n        // The larger value between not selecting and selecting item i\n        dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[cap];\n}\n
knapsack.rs
/* 0-1 knapsack: Space-optimized dynamic programming */\nfn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize dp table\n    let mut dp = vec![0; cap + 1];\n    // State transition\n    for i in 1..=n {\n        // Traverse in reverse order\n        for c in (1..=cap).rev() {\n            if wgt[i - 1] <= c as i32 {\n                // The larger value between not selecting and selecting item i\n                dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    dp[cap]\n}\n
knapsack.c
/* 0-1 knapsack: Space-optimized dynamic programming */\nint knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize dp table\n    int *dp = calloc(cap + 1, sizeof(int));\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        // Traverse in reverse order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[cap];\n    // Free memory\n    free(dp);\n    return res;\n}\n
knapsack.kt
/* 0-1 knapsack: Space-optimized dynamic programming */\nfun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize dp table\n    val dp = IntArray(cap + 1)\n    // State transition\n    for (i in 1..n) {\n        // Traverse in reverse order\n        for (c in cap downTo 1) {\n            if (wgt[i - 1] <= c) {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
knapsack.rb
### 0-1 knapsack: space-optimized DP ###\ndef knapsack_dp_comp(wgt, val, cap)\n  n = wgt.length\n  # Initialize dp table\n  dp = Array.new(cap + 1, 0)\n  # State transition\n  for i in 1...(n + 1)\n    # Traverse in reverse order\n    for c in cap.downto(1)\n      if wgt[i - 1] > c\n        # If exceeds knapsack capacity, don't select item i\n        dp[c] = dp[c]\n      else\n        # The larger value between not selecting and selecting item i\n        dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[cap]\nend\n
","path":["Chapter 14. Dynamic Programming","14.4   0-1 Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/summary/","level":1,"title":"14.7   Summary","text":"","path":["Chapter 14. Dynamic Programming","14.7   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Dynamic programming decomposes problems and avoids redundant computation by storing the solutions to subproblems, thereby significantly improving computational efficiency.
  • Without considering time constraints, all dynamic programming problems can be solved using backtracking (brute force search), but the recursion tree contains a large number of overlapping subproblems, resulting in extremely low efficiency. By introducing a memo list, we can store the solutions to all computed subproblems, ensuring that overlapping subproblems are only computed once.
  • Memoization is a top-down recursive solution, while the corresponding dynamic programming is a bottom-up iterative solution, similar to \"filling in a table\". Since the current state only depends on certain local states, we can eliminate one dimension of the \\(dp\\) table to reduce space complexity.
  • Subproblem decomposition is a general algorithmic approach, with different properties in divide and conquer, dynamic programming, and backtracking.
  • Dynamic programming problems have three major characteristics: overlapping subproblems, optimal substructure, and no aftereffects.
  • If the optimal solution to the original problem can be constructed from the optimal solutions to the subproblems, then it has optimal substructure.
  • No aftereffects means that for a given state, its future development is only related to that state and has nothing to do with all past states. Many combinatorial optimization problems do not have no aftereffects and cannot be quickly solved using dynamic programming.

Knapsack problem

  • The knapsack problem is one of the most typical dynamic programming problems, with variants such as the 0-1 knapsack, unbounded knapsack, and multiple knapsack.
  • The state definition for the 0-1 knapsack is the maximum value among the first \\(i\\) items in a knapsack of capacity \\(c\\). Based on the two decisions of not putting an item in the knapsack and putting it in, the optimal substructure can be identified and the state transition equation constructed. In space optimization, since each state depends on the state directly above and to the upper-left, the list needs to be traversed in reverse order to avoid overwriting the upper-left state.
  • The unbounded knapsack problem has no limit on the selection quantity of each type of item, so the state transition for choosing to put in an item differs from the 0-1 knapsack problem. Since the state depends on the state directly above and directly to the left, space optimization should use forward traversal.
  • The coin change problem is a variant of the unbounded knapsack problem. It changes from seeking the \"maximum\" value to seeking the \"minimum\" number of coins, so \\(\\max()\\) in the state transition equation should be changed to \\(\\min()\\). It changes from seeking \"not exceeding\" the knapsack capacity to seeking \"exactly\" making up the target amount, so \\(amt + 1\\) is used to represent the invalid solution of \"unable to make up the target amount\".
  • Coin change problem II changes from seeking the \"minimum number of coins\" to seeking the \"number of coin combinations\", so the state transition equation correspondingly changes from \\(\\min()\\) to a summation operator.

Edit distance problem

  • Edit distance (Levenshtein distance) is used to measure the similarity between two strings, defined as the minimum number of edit steps from one string to another, with edit operations including insert, delete, and replace.
  • The state definition for the edit distance problem is the minimum number of edit steps required to change the first \\(i\\) characters of \\(s\\) into the first \\(j\\) characters of \\(t\\). When \\(s[i] \\ne t[j]\\), there are three decisions: insert, delete, replace, each with corresponding remaining subproblems. From this, the optimal substructure can be identified and the state transition equation constructed. When \\(s[i] = t[j]\\), no edit is required for the current character.
  • In edit distance, the state depends on the state directly above, directly to the left, and to the upper-left, so after space optimization, neither forward nor reverse traversal can correctly perform state transitions. For this reason, we use a variable to temporarily store the upper-left state, thus transforming to a situation equivalent to the unbounded knapsack problem, allowing for forward traversal after space optimization.
","path":["Chapter 14. Dynamic Programming","14.7   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/","level":1,"title":"14.5   Unbounded Knapsack Problem","text":"

In this section, we first solve another common knapsack problem: the unbounded knapsack, and then explore a special case of it: the coin change problem.

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1451-unbounded-knapsack-problem","level":2,"title":"14.5.1   Unbounded Knapsack Problem","text":"

Question

Given \\(n\\) items, where the weight of the \\(i\\)-th item is \\(wgt[i-1]\\) and its value is \\(val[i-1]\\), and a knapsack with capacity \\(cap\\). Each item can be selected multiple times. What is the maximum value that can be placed in the knapsack within the capacity limit? An example is shown in Figure 14-22.

Figure 14-22   Example data for unbounded knapsack problem

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach","level":3,"title":"1.   Dynamic Programming Approach","text":"

The unbounded knapsack problem is very similar to the 0-1 knapsack problem, differing only in that there is no limit on the number of times an item can be selected.

  • In the 0-1 knapsack problem, there is only one of each type of item, so after placing item \\(i\\) in the knapsack, we can only choose from the first \\(i-1\\) items.
  • In the unbounded knapsack problem, the quantity of each type of item is unlimited, so after placing item \\(i\\) in the knapsack, we can still choose from the first \\(i\\) items.

Under the rules of the unbounded knapsack problem, the changes in state \\([i, c]\\) are divided into two cases.

  • Not putting item \\(i\\): Same as the 0-1 knapsack problem, transfer to \\([i-1, c]\\).
  • Putting item \\(i\\): Different from the 0-1 knapsack problem, transfer to \\([i, c-wgt[i-1]]\\).

Thus, the state transition equation becomes:

\\[ dp[i, c] = \\max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) \\]","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"

Comparing the code for the two problems, there is one change in state transition from \\(i-1\\) to \\(i\\), with everything else identical:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby unbounded_knapsack.py
def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Unbounded knapsack: Dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize dp table\n    dp = [[0] * (cap + 1) for _ in range(n + 1)]\n    # State transition\n    for i in range(1, n + 1):\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            else:\n                # The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])\n    return dp[n][cap]\n
unbounded_knapsack.cpp
/* Unbounded knapsack: Dynamic programming */\nint unboundedKnapsackDP(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize dp table\n    vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0));\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
unbounded_knapsack.java
/* Unbounded knapsack: Dynamic programming */\nint unboundedKnapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize dp table\n    int[][] dp = new int[n + 1][cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
unbounded_knapsack.cs
/* Unbounded knapsack: Dynamic programming */\nint UnboundedKnapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.Length;\n    // Initialize dp table\n    int[,] dp = new int[n + 1, cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i, c] = dp[i - 1, c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n, cap];\n}\n
unbounded_knapsack.go
/* Unbounded knapsack: Dynamic programming */\nfunc unboundedKnapsackDP(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, cap+1)\n    }\n    // State transition\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i-1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
unbounded_knapsack.swift
/* Unbounded knapsack: Dynamic programming */\nfunc unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize dp table\n    var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1)\n    // State transition\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
unbounded_knapsack.js
/* Unbounded knapsack: Dynamic programming */\nfunction unboundedKnapsackDP(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
unbounded_knapsack.ts
/* Unbounded knapsack: Dynamic programming */\nfunction unboundedKnapsackDP(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
unbounded_knapsack.dart
/* Unbounded knapsack: Dynamic programming */\nint unboundedKnapsackDP(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0));\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If exceeds knapsack capacity, don't select item i\n        dp[i][c] = dp[i - 1][c];\n      } else {\n        // The larger value between not selecting and selecting item i\n        dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[n][cap];\n}\n
unbounded_knapsack.rs
/* Unbounded knapsack: Dynamic programming */\nfn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize dp table\n    let mut dp = vec![vec![0; cap + 1]; n + 1];\n    // State transition\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
unbounded_knapsack.c
/* Unbounded knapsack: Dynamic programming */\nint unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(cap + 1, sizeof(int));\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[n][cap];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
unbounded_knapsack.kt
/* Unbounded knapsack: Dynamic programming */\nfun unboundedKnapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize dp table\n    val dp = Array(n + 1) { IntArray(cap + 1) }\n    // State transition\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
unbounded_knapsack.rb
### Unbounded knapsack: dynamic programming ###\ndef unbounded_knapsack_dp(wgt, val, cap)\n  n = wgt.length\n  # Initialize dp table\n  dp = Array.new(n + 1) { Array.new(cap + 1, 0) }\n  # State transition\n  for i in 1...(n + 1)\n    for c in 1...(cap + 1)\n      if wgt[i - 1] > c\n        # If exceeds knapsack capacity, don't select item i\n        dp[i][c] = dp[i - 1][c]\n      else\n        # The larger value between not selecting and selecting item i\n        dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[n][cap]\nend\n
","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization","level":3,"title":"3.   Space Optimization","text":"

Since the current state is transferred from states on the left and above, after space optimization, each row in the \\(dp\\) table should be traversed in forward order.

This traversal order is exactly opposite to the 0-1 knapsack. Please refer to Figure 14-23 to understand the difference between the two.

<1><2><3><4><5><6>

Figure 14-23   Space-optimized dynamic programming process for unbounded knapsack problem

The code implementation is relatively simple, just delete the first dimension of the array dp:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby unbounded_knapsack.py
def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Unbounded knapsack: Space-optimized dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize dp table\n    dp = [0] * (cap + 1)\n    # State transition\n    for i in range(1, n + 1):\n        # Traverse in forward order\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c]\n            else:\n                # The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n    return dp[cap]\n
unbounded_knapsack.cpp
/* Unbounded knapsack: Space-optimized dynamic programming */\nint unboundedKnapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize dp table\n    vector<int> dp(cap + 1, 0);\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
unbounded_knapsack.java
/* Unbounded knapsack: Space-optimized dynamic programming */\nint unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize dp table\n    int[] dp = new int[cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
unbounded_knapsack.cs
/* Unbounded knapsack: Space-optimized dynamic programming */\nint UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.Length;\n    // Initialize dp table\n    int[] dp = new int[cap + 1];\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
unbounded_knapsack.go
/* Unbounded knapsack: Space-optimized dynamic programming */\nfunc unboundedKnapsackDPComp(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize dp table\n    dp := make([]int, cap+1)\n    // State transition\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[cap]\n}\n
unbounded_knapsack.swift
/* Unbounded knapsack: Space-optimized dynamic programming */\nfunc unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize dp table\n    var dp = Array(repeating: 0, count: cap + 1)\n    // State transition\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
unbounded_knapsack.js
/* Unbounded knapsack: Space-optimized dynamic programming */\nfunction unboundedKnapsackDPComp(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array.from({ length: cap + 1 }, () => 0);\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
unbounded_knapsack.ts
/* Unbounded knapsack: Space-optimized dynamic programming */\nfunction unboundedKnapsackDPComp(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize dp table\n    const dp = Array.from({ length: cap + 1 }, () => 0);\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
unbounded_knapsack.dart
/* Unbounded knapsack: Space-optimized dynamic programming */\nint unboundedKnapsackDPComp(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize dp table\n  List<int> dp = List.filled(cap + 1, 0);\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If exceeds knapsack capacity, don't select item i\n        dp[c] = dp[c];\n      } else {\n        // The larger value between not selecting and selecting item i\n        dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[cap];\n}\n
unbounded_knapsack.rs
/* Unbounded knapsack: Space-optimized dynamic programming */\nfn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize dp table\n    let mut dp = vec![0; cap + 1];\n    // State transition\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    dp[cap]\n}\n
unbounded_knapsack.c
/* Unbounded knapsack: Space-optimized dynamic programming */\nint unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize dp table\n    int *dp = calloc(cap + 1, sizeof(int));\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c];\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[cap];\n    // Free memory\n    free(dp);\n    return res;\n}\n
unbounded_knapsack.kt
/* Unbounded knapsack: Space-optimized dynamic programming */\nfun unboundedKnapsackDPComp(\n    wgt: IntArray,\n    _val: IntArray,\n    cap: Int\n): Int {\n    val n = wgt.size\n    // Initialize dp table\n    val dp = IntArray(cap + 1)\n    // State transition\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If exceeds knapsack capacity, don't select item i\n                dp[c] = dp[c]\n            } else {\n                // The larger value between not selecting and selecting item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
unbounded_knapsack.rb
### Unbounded knapsack: space-optimized DP ###\ndef unbounded_knapsack_dp_comp(wgt, val, cap)\n  n = wgt.length\n  # Initialize dp table\n  dp = Array.new(cap + 1, 0)\n  # State transition\n  for i in 1...(n + 1)\n    # Traverse in forward order\n    for c in 1...(cap + 1)\n      if wgt[i -1] > c\n        # If exceeds knapsack capacity, don't select item i\n        dp[c] = dp[c]\n      else\n        # The larger value between not selecting and selecting item i\n        dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[cap]\nend\n
","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1452-coin-change-problem","level":2,"title":"14.5.2   Coin Change Problem","text":"

The knapsack problem represents a large class of dynamic programming problems and has many variants, such as the coin change problem.

Question

Given \\(n\\) types of coins, where the denomination of the \\(i\\)-th type of coin is \\(coins[i - 1]\\), and the target amount is \\(amt\\). Each type of coin can be selected multiple times. What is the minimum number of coins needed to make up the target amount? If it is impossible to make up the target amount, return \\(-1\\). An example is shown in Figure 14-24.

Figure 14-24   Example data for coin change problem

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach_1","level":3,"title":"1.   Dynamic Programming Approach","text":"

The coin change problem can be viewed as a special case of the unbounded knapsack problem, with the following connections and differences.

  • The two problems can be converted to each other: \"item\" corresponds to \"coin\", \"item weight\" corresponds to \"coin denomination\", and \"knapsack capacity\" corresponds to \"target amount\".
  • The optimization goals are opposite: the unbounded knapsack problem aims to maximize item value, while the coin change problem aims to minimize the number of coins.
  • The unbounded knapsack problem seeks solutions \"not exceeding\" the knapsack capacity, while the coin change problem seeks solutions that \"exactly\" make up the target amount.

Step 1: Think about the decisions in each round, define the state, and thus obtain the \\(dp\\) table

State \\([i, a]\\) corresponds to the subproblem: the minimum number of coins among the first \\(i\\) types of coins that can make up amount \\(a\\), denoted as \\(dp[i, a]\\).

The two-dimensional \\(dp\\) table has size \\((n+1) \\times (amt+1)\\).

Step 2: Identify the optimal substructure, and then derive the state transition equation

This problem differs from the unbounded knapsack problem in the following two aspects regarding the state transition equation.

  • This problem seeks the minimum value, so the operator \\(\\max()\\) needs to be changed to \\(\\min()\\).
  • The optimization target is the number of coins rather than item value, so when a coin is selected, simply execute \\(+1\\).
\\[ dp[i, a] = \\min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) \\]

Step 3: Determine boundary conditions and state transition order

When the target amount is \\(0\\), the minimum number of coins needed to make it up is \\(0\\), so all \\(dp[i, 0]\\) in the first column equal \\(0\\).

When there are no coins, it is impossible to make up any amount \\(> 0\\), which is an invalid solution. To enable the \\(\\min()\\) function in the state transition equation to identify and filter out invalid solutions, we consider using \\(+ \\infty\\) to represent them, i.e., set all \\(dp[0, a]\\) in the first row to \\(+ \\infty\\).

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation_1","level":3,"title":"2.   Code Implementation","text":"

Most programming languages do not provide a \\(+ \\infty\\) variable, and can only use the maximum value of integer type int as a substitute. However, this can lead to large number overflow: the \\(+ 1\\) operation in the state transition equation may cause overflow.

For this reason, we use the number \\(amt + 1\\) to represent invalid solutions, because the maximum number of coins needed to make up \\(amt\\) is at most \\(amt\\). Before returning, check whether \\(dp[n, amt]\\) equals \\(amt + 1\\); if so, return \\(-1\\), indicating that the target amount cannot be made up. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change.py
def coin_change_dp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: Dynamic programming\"\"\"\n    n = len(coins)\n    MAX = amt + 1\n    # Initialize dp table\n    dp = [[0] * (amt + 1) for _ in range(n + 1)]\n    # State transition: first row and first column\n    for a in range(1, amt + 1):\n        dp[0][a] = MAX\n    # State transition: rest of the rows and columns\n    for i in range(1, n + 1):\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            else:\n                # The smaller value between not selecting and selecting coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n    return dp[n][amt] if dp[n][amt] != MAX else -1\n
coin_change.cpp
/* Coin change: Dynamic programming */\nint coinChangeDP(vector<int> &coins, int amt) {\n    int n = coins.size();\n    int MAX = amt + 1;\n    // Initialize dp table\n    vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));\n    // State transition: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
coin_change.java
/* Coin change: Dynamic programming */\nint coinChangeDP(int[] coins, int amt) {\n    int n = coins.length;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int[][] dp = new int[n + 1][amt + 1];\n    // State transition: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
coin_change.cs
/* Coin change: Dynamic programming */\nint CoinChangeDP(int[] coins, int amt) {\n    int n = coins.Length;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int[,] dp = new int[n + 1, amt + 1];\n    // State transition: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0, a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i, a] = dp[i - 1, a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n, amt] != MAX ? dp[n, amt] : -1;\n}\n
coin_change.go
/* Coin change: Dynamic programming */\nfunc coinChangeDP(coins []int, amt int) int {\n    n := len(coins)\n    max := amt + 1\n    // Initialize dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, amt+1)\n    }\n    // State transition: first row and first column\n    for a := 1; a <= amt; a++ {\n        dp[0][a] = max\n    }\n    // State transition: rest of the rows and columns\n    for i := 1; i <= n; i++ {\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i-1][a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1)))\n            }\n        }\n    }\n    if dp[n][amt] != max {\n        return dp[n][amt]\n    }\n    return -1\n}\n
coin_change.swift
/* Coin change: Dynamic programming */\nfunc coinChangeDP(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    let MAX = amt + 1\n    // Initialize dp table\n    var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1)\n    // State transition: first row and first column\n    for a in 1 ... amt {\n        dp[0][a] = MAX\n    }\n    // State transition: rest of the rows and columns\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1\n}\n
coin_change.js
/* Coin change: Dynamic programming */\nfunction coinChangeDP(coins, amt) {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // State transition: first row and first column\n    for (let a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] !== MAX ? dp[n][amt] : -1;\n}\n
coin_change.ts
/* Coin change: Dynamic programming */\nfunction coinChangeDP(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // State transition: first row and first column\n    for (let a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] !== MAX ? dp[n][amt] : -1;\n}\n
coin_change.dart
/* Coin change: Dynamic programming */\nint coinChangeDP(List<int> coins, int amt) {\n  int n = coins.length;\n  int MAX = amt + 1;\n  // Initialize dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0));\n  // State transition: first row and first column\n  for (int a = 1; a <= amt; a++) {\n    dp[0][a] = MAX;\n  }\n  // State transition: rest of the rows and columns\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If exceeds target amount, don't select coin i\n        dp[i][a] = dp[i - 1][a];\n      } else {\n        // The smaller value between not selecting and selecting coin i\n        dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n      }\n    }\n  }\n  return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
coin_change.rs
/* Coin change: Dynamic programming */\nfn coin_change_dp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    let max = amt + 1;\n    // Initialize dp table\n    let mut dp = vec![vec![0; amt + 1]; n + 1];\n    // State transition: first row and first column\n    for a in 1..=amt {\n        dp[0][a] = max;\n    }\n    // State transition: rest of the rows and columns\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1);\n            }\n        }\n    }\n    if dp[n][amt] != max {\n        return dp[n][amt] as i32;\n    } else {\n        -1\n    }\n}\n
coin_change.c
/* Coin change: Dynamic programming */\nint coinChangeDP(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(amt + 1, sizeof(int));\n    }\n    // State transition: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transition: rest of the rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    int res = dp[n][amt] != MAX ? dp[n][amt] : -1;\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
coin_change.kt
/* Coin change: Dynamic programming */\nfun coinChangeDP(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    val MAX = amt + 1\n    // Initialize dp table\n    val dp = Array(n + 1) { IntArray(amt + 1) }\n    // State transition: first row and first column\n    for (a in 1..amt) {\n        dp[0][a] = MAX\n    }\n    // State transition: rest of the rows and columns\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return if (dp[n][amt] != MAX) dp[n][amt] else -1\n}\n
coin_change.rb
### Coin change: dynamic programming ###\ndef coin_change_dp(coins, amt)\n  n = coins.length\n  _MAX = amt + 1\n  # Initialize dp table\n  dp = Array.new(n + 1) { Array.new(amt + 1, 0) }\n  # State transition: first row and first column\n  (1...(amt + 1)).each { |a| dp[0][a] = _MAX }\n  # State transition: rest of the rows and columns\n  for i in 1...(n + 1)\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If exceeds target amount, don't select coin i\n        dp[i][a] = dp[i - 1][a]\n      else\n        # The smaller value between not selecting and selecting coin i\n        dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min\n      end\n    end\n  end\n  dp[n][amt] != _MAX ? dp[n][amt] : -1\nend\n

Figure 14-25 shows the dynamic programming process for coin change, which is very similar to the unbounded knapsack problem.

<1><2><3><4><5><6><7><8><9><10><11><12><13><14><15>

Figure 14-25   Dynamic programming process for coin change problem

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization_1","level":3,"title":"3.   Space Optimization","text":"

The space optimization for the coin change problem is handled in the same way as the unbounded knapsack problem:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change.py
def coin_change_dp_comp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: Space-optimized dynamic programming\"\"\"\n    n = len(coins)\n    MAX = amt + 1\n    # Initialize dp table\n    dp = [MAX] * (amt + 1)\n    dp[0] = 0\n    # State transition\n    for i in range(1, n + 1):\n        # Traverse in forward order\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            else:\n                # The smaller value between not selecting and selecting coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n    return dp[amt] if dp[amt] != MAX else -1\n
coin_change.cpp
/* Coin change: Space-optimized dynamic programming */\nint coinChangeDPComp(vector<int> &coins, int amt) {\n    int n = coins.size();\n    int MAX = amt + 1;\n    // Initialize dp table\n    vector<int> dp(amt + 1, MAX);\n    dp[0] = 0;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
coin_change.java
/* Coin change: Space-optimized dynamic programming */\nint coinChangeDPComp(int[] coins, int amt) {\n    int n = coins.length;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int[] dp = new int[amt + 1];\n    Arrays.fill(dp, MAX);\n    dp[0] = 0;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
coin_change.cs
/* Coin change: Space-optimized dynamic programming */\nint CoinChangeDPComp(int[] coins, int amt) {\n    int n = coins.Length;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int[] dp = new int[amt + 1];\n    Array.Fill(dp, MAX);\n    dp[0] = 0;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
coin_change.go
/* Coin change: Dynamic programming */\nfunc coinChangeDPComp(coins []int, amt int) int {\n    n := len(coins)\n    max := amt + 1\n    // Initialize dp table\n    dp := make([]int, amt+1)\n    for i := 1; i <= amt; i++ {\n        dp[i] = max\n    }\n    // State transition\n    for i := 1; i <= n; i++ {\n        // Traverse in forward order\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1)))\n            }\n        }\n    }\n    if dp[amt] != max {\n        return dp[amt]\n    }\n    return -1\n}\n
coin_change.swift
/* Coin change: Space-optimized dynamic programming */\nfunc coinChangeDPComp(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    let MAX = amt + 1\n    // Initialize dp table\n    var dp = Array(repeating: MAX, count: amt + 1)\n    dp[0] = 0\n    // State transition\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1\n}\n
coin_change.js
/* Coin change: Space-optimized dynamic programming */\nfunction coinChangeDPComp(coins, amt) {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize dp table\n    const dp = Array.from({ length: amt + 1 }, () => MAX);\n    dp[0] = 0;\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] !== MAX ? dp[amt] : -1;\n}\n
coin_change.ts
/* Coin change: Space-optimized dynamic programming */\nfunction coinChangeDPComp(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize dp table\n    const dp = Array.from({ length: amt + 1 }, () => MAX);\n    dp[0] = 0;\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] !== MAX ? dp[amt] : -1;\n}\n
coin_change.dart
/* Coin change: Space-optimized dynamic programming */\nint coinChangeDPComp(List<int> coins, int amt) {\n  int n = coins.length;\n  int MAX = amt + 1;\n  // Initialize dp table\n  List<int> dp = List.filled(amt + 1, MAX);\n  dp[0] = 0;\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If exceeds target amount, don't select coin i\n        dp[a] = dp[a];\n      } else {\n        // The smaller value between not selecting and selecting coin i\n        dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);\n      }\n    }\n  }\n  return dp[amt] != MAX ? dp[amt] : -1;\n}\n
coin_change.rs
/* Coin change: Space-optimized dynamic programming */\nfn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    let max = amt + 1;\n    // Initialize dp table\n    let mut dp = vec![0; amt + 1];\n    dp.fill(max);\n    dp[0] = 0;\n    // State transition\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1);\n            }\n        }\n    }\n    if dp[amt] != max {\n        return dp[amt] as i32;\n    } else {\n        -1\n    }\n}\n
coin_change.c
/* Coin change: Space-optimized dynamic programming */\nint coinChangeDPComp(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    int MAX = amt + 1;\n    // Initialize dp table\n    int *dp = malloc((amt + 1) * sizeof(int));\n    for (int j = 1; j <= amt; j++) {\n        dp[j] = MAX;\n    } \n    dp[0] = 0;\n\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    int res = dp[amt] != MAX ? dp[amt] : -1;\n    // Free memory\n    free(dp);\n    return res;\n}\n
coin_change.kt
/* Coin change: Space-optimized dynamic programming */\nfun coinChangeDPComp(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    val MAX = amt + 1\n    // Initialize dp table\n    val dp = IntArray(amt + 1)\n    dp.fill(MAX)\n    dp[0] = 0\n    // State transition\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // The smaller value between not selecting and selecting coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return if (dp[amt] != MAX) dp[amt] else -1\n}\n
coin_change.rb
### Coin change: space-optimized DP ###\ndef coin_change_dp_comp(coins, amt)\n  n = coins.length\n  _MAX = amt + 1\n  # Initialize dp table\n  dp = Array.new(amt + 1, _MAX)\n  dp[0] = 0\n  # State transition\n  for i in 1...(n + 1)\n    # Traverse in forward order\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If exceeds target amount, don't select coin i\n        dp[a] = dp[a]\n      else\n        # The smaller value between not selecting and selecting coin i\n        dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min\n      end\n    end\n  end\n  dp[amt] != _MAX ? dp[amt] : -1\nend\n
","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1453-coin-change-problem-ii","level":2,"title":"14.5.3   Coin Change Problem Ii","text":"

Question

Given \\(n\\) types of coins, where the denomination of the \\(i\\)-th type of coin is \\(coins[i - 1]\\), and the target amount is \\(amt\\). Each type of coin can be selected multiple times. What is the number of coin combinations that can make up the target amount? An example is shown in Figure 14-26.

Figure 14-26   Example data for coin change problem II

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach_2","level":3,"title":"1.   Dynamic Programming Approach","text":"

Compared to the previous problem, this problem's goal is to find the number of combinations, so the subproblem becomes: the number of combinations among the first \\(i\\) types of coins that can make up amount \\(a\\). The \\(dp\\) table remains a two-dimensional matrix of size \\((n+1) \\times (amt + 1)\\).

The number of combinations for the current state equals the sum of the combinations from not selecting the current coin and selecting the current coin. The state transition equation is:

\\[ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] \\]

When the target amount is \\(0\\), no coins need to be selected to make up the target amount, so all \\(dp[i, 0]\\) in the first column should be initialized to \\(1\\). When there are no coins, it is impossible to make up any amount \\(>0\\), so all \\(dp[0, a]\\) in the first row equal \\(0\\).

","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation_2","level":3,"title":"2.   Code Implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_ii.py
def coin_change_ii_dp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change II: Dynamic programming\"\"\"\n    n = len(coins)\n    # Initialize dp table\n    dp = [[0] * (amt + 1) for _ in range(n + 1)]\n    # Initialize first column\n    for i in range(n + 1):\n        dp[i][0] = 1\n    # State transition\n    for i in range(1, n + 1):\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            else:\n                # Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n    return dp[n][amt]\n
coin_change_ii.cpp
/* Coin change II: Dynamic programming */\nint coinChangeIIDP(vector<int> &coins, int amt) {\n    int n = coins.size();\n    // Initialize dp table\n    vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));\n    // Initialize first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
coin_change_ii.java
/* Coin change II: Dynamic programming */\nint coinChangeIIDP(int[] coins, int amt) {\n    int n = coins.length;\n    // Initialize dp table\n    int[][] dp = new int[n + 1][amt + 1];\n    // Initialize first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
coin_change_ii.cs
/* Coin change II: Dynamic programming */\nint CoinChangeIIDP(int[] coins, int amt) {\n    int n = coins.Length;\n    // Initialize dp table\n    int[,] dp = new int[n + 1, amt + 1];\n    // Initialize first column\n    for (int i = 0; i <= n; i++) {\n        dp[i, 0] = 1;\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i, a] = dp[i - 1, a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n, amt];\n}\n
coin_change_ii.go
/* Coin change II: Dynamic programming */\nfunc coinChangeIIDP(coins []int, amt int) int {\n    n := len(coins)\n    // Initialize dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, amt+1)\n    }\n    // Initialize first column\n    for i := 0; i <= n; i++ {\n        dp[i][0] = 1\n    }\n    // State transition: rest of the rows and columns\n    for i := 1; i <= n; i++ {\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i-1][a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
coin_change_ii.swift
/* Coin change II: Dynamic programming */\nfunc coinChangeIIDP(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    // Initialize dp table\n    var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1)\n    // Initialize first column\n    for i in 0 ... n {\n        dp[i][0] = 1\n    }\n    // State transition\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
coin_change_ii.js
/* Coin change II: Dynamic programming */\nfunction coinChangeIIDP(coins, amt) {\n    const n = coins.length;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // Initialize first column\n    for (let i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
coin_change_ii.ts
/* Coin change II: Dynamic programming */\nfunction coinChangeIIDP(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    // Initialize dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // Initialize first column\n    for (let i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
coin_change_ii.dart
/* Coin change II: Dynamic programming */\nint coinChangeIIDP(List<int> coins, int amt) {\n  int n = coins.length;\n  // Initialize dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0));\n  // Initialize first column\n  for (int i = 0; i <= n; i++) {\n    dp[i][0] = 1;\n  }\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If exceeds target amount, don't select coin i\n        dp[i][a] = dp[i - 1][a];\n      } else {\n        // Sum of the two options: not selecting and selecting coin i\n        dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n      }\n    }\n  }\n  return dp[n][amt];\n}\n
coin_change_ii.rs
/* Coin change II: Dynamic programming */\nfn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    // Initialize dp table\n    let mut dp = vec![vec![0; amt + 1]; n + 1];\n    // Initialize first column\n    for i in 0..=n {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize];\n            }\n        }\n    }\n    dp[n][amt]\n}\n
coin_change_ii.c
/* Coin change II: Dynamic programming */\nint coinChangeIIDP(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    // Initialize dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(amt + 1, sizeof(int));\n    }\n    // Initialize first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    int res = dp[n][amt];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
coin_change_ii.kt
/* Coin change II: Dynamic programming */\nfun coinChangeIIDP(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    // Initialize dp table\n    val dp = Array(n + 1) { IntArray(amt + 1) }\n    // Initialize first column\n    for (i in 0..n) {\n        dp[i][0] = 1\n    }\n    // State transition\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
coin_change_ii.rb
### Coin change II: dynamic programming ###\ndef coin_change_ii_dp(coins, amt)\n  n = coins.length\n  # Initialize dp table\n  dp = Array.new(n + 1) { Array.new(amt + 1, 0) }\n  # Initialize first column\n  (0...(n + 1)).each { |i| dp[i][0] = 1 }\n  # State transition\n  for i in 1...(n + 1)\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If exceeds target amount, don't select coin i\n        dp[i][a] = dp[i - 1][a]\n      else\n        # Sum of the two options: not selecting and selecting coin i\n        dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n      end\n    end\n  end\n  dp[n][amt]\nend\n
","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization_2","level":3,"title":"3.   Space Optimization","text":"

The space optimization is handled in the same way, just delete the coin dimension:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_ii.py
def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change II: Space-optimized dynamic programming\"\"\"\n    n = len(coins)\n    # Initialize dp table\n    dp = [0] * (amt + 1)\n    dp[0] = 1\n    # State transition\n    for i in range(1, n + 1):\n        # Traverse in forward order\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            else:\n                # Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n    return dp[amt]\n
coin_change_ii.cpp
/* Coin change II: Space-optimized dynamic programming */\nint coinChangeIIDPComp(vector<int> &coins, int amt) {\n    int n = coins.size();\n    // Initialize dp table\n    vector<int> dp(amt + 1, 0);\n    dp[0] = 1;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
coin_change_ii.java
/* Coin change II: Space-optimized dynamic programming */\nint coinChangeIIDPComp(int[] coins, int amt) {\n    int n = coins.length;\n    // Initialize dp table\n    int[] dp = new int[amt + 1];\n    dp[0] = 1;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
coin_change_ii.cs
/* Coin change II: Space-optimized dynamic programming */\nint CoinChangeIIDPComp(int[] coins, int amt) {\n    int n = coins.Length;\n    // Initialize dp table\n    int[] dp = new int[amt + 1];\n    dp[0] = 1;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
coin_change_ii.go
/* Coin change II: Space-optimized dynamic programming */\nfunc coinChangeIIDPComp(coins []int, amt int) int {\n    n := len(coins)\n    // Initialize dp table\n    dp := make([]int, amt+1)\n    dp[0] = 1\n    // State transition\n    for i := 1; i <= n; i++ {\n        // Traverse in forward order\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a-coins[i-1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
coin_change_ii.swift
/* Coin change II: Space-optimized dynamic programming */\nfunc coinChangeIIDPComp(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    // Initialize dp table\n    var dp = Array(repeating: 0, count: amt + 1)\n    dp[0] = 1\n    // State transition\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
coin_change_ii.js
/* Coin change II: Space-optimized dynamic programming */\nfunction coinChangeIIDPComp(coins, amt) {\n    const n = coins.length;\n    // Initialize dp table\n    const dp = Array.from({ length: amt + 1 }, () => 0);\n    dp[0] = 1;\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
coin_change_ii.ts
/* Coin change II: Space-optimized dynamic programming */\nfunction coinChangeIIDPComp(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    // Initialize dp table\n    const dp = Array.from({ length: amt + 1 }, () => 0);\n    dp[0] = 1;\n    // State transition\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
coin_change_ii.dart
/* Coin change II: Space-optimized dynamic programming */\nint coinChangeIIDPComp(List<int> coins, int amt) {\n  int n = coins.length;\n  // Initialize dp table\n  List<int> dp = List.filled(amt + 1, 0);\n  dp[0] = 1;\n  // State transition\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If exceeds target amount, don't select coin i\n        dp[a] = dp[a];\n      } else {\n        // Sum of the two options: not selecting and selecting coin i\n        dp[a] = dp[a] + dp[a - coins[i - 1]];\n      }\n    }\n  }\n  return dp[amt];\n}\n
coin_change_ii.rs
/* Coin change II: Space-optimized dynamic programming */\nfn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    // Initialize dp table\n    let mut dp = vec![0; amt + 1];\n    dp[0] = 1;\n    // State transition\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1] as usize];\n            }\n        }\n    }\n    dp[amt]\n}\n
coin_change_ii.c
/* Coin change II: Space-optimized dynamic programming */\nint coinChangeIIDPComp(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    // Initialize dp table\n    int *dp = calloc(amt + 1, sizeof(int));\n    dp[0] = 1;\n    // State transition\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a];\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    int res = dp[amt];\n    // Free memory\n    free(dp);\n    return res;\n}\n
coin_change_ii.kt
/* Coin change II: Space-optimized dynamic programming */\nfun coinChangeIIDPComp(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    // Initialize dp table\n    val dp = IntArray(amt + 1)\n    dp[0] = 1\n    // State transition\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If exceeds target amount, don't select coin i\n                dp[a] = dp[a]\n            } else {\n                // Sum of the two options: not selecting and selecting coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
coin_change_ii.rb
### Coin change II: space-optimized DP ###\ndef coin_change_ii_dp_comp(coins, amt)\n  n = coins.length\n  # Initialize dp table\n  dp = Array.new(amt + 1, 0)\n  dp[0] = 1\n  # State transition\n  for i in 1...(n + 1)\n    # Traverse in forward order\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If exceeds target amount, don't select coin i\n        dp[a] = dp[a]\n      else\n        # Sum of the two options: not selecting and selecting coin i\n        dp[a] = dp[a] + dp[a - coins[i - 1]]\n      end\n    end\n  end\n  dp[amt]\nend\n
","path":["Chapter 14. Dynamic Programming","14.5   Unbounded Knapsack Problem"],"tags":[]},{"location":"chapter_graph/","level":1,"title":"Chapter 9.   Graph","text":"

Abstract

In the journey of life, we are like nodes, connected by countless invisible edges.

Each encounter and parting leaves a unique mark on this vast network graph.

","path":["Chapter 9. Graph","Chapter 9.   Graph"],"tags":[]},{"location":"chapter_graph/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 9.1   Graph
  • 9.2   Basic Operations on Graphs
  • 9.3   Graph Traversal
  • 9.4   Summary
","path":["Chapter 9. Graph","Chapter 9.   Graph"],"tags":[]},{"location":"chapter_graph/graph/","level":1,"title":"9.1   Graph","text":"

A graph is a nonlinear data structure consisting of vertices and edges. We can abstractly represent a graph \\(G\\) as a set of vertices \\(V\\) and a set of edges \\(E\\). The following example shows a graph containing 5 vertices and 7 edges.

\\[ \\begin{aligned} V & = \\{ 1, 2, 3, 4, 5 \\} \\newline E & = \\{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \\} \\newline G & = \\{ V, E \\} \\newline \\end{aligned} \\]

If we view vertices as nodes and edges as references (pointers) connecting the nodes, we can see graphs as a data structure extended from linked lists. As shown in Figure 9-1, compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) have a higher degree of freedom and are therefore more complex.

Figure 9-1   Relationships among linked lists, trees, and graphs

","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph/#911-common-types-and-terminology-of-graphs","level":2,"title":"9.1.1   Common Types and Terminology of Graphs","text":"

Graphs can be divided into undirected graphs and directed graphs based on whether edges have direction, as shown in Figure 9-2.

  • In undirected graphs, edges represent a \"bidirectional\" connection between two vertices, such as the \"friend relationship\" on WeChat or QQ.
  • In directed graphs, edges have directionality, meaning edges \\(A \\rightarrow B\\) and \\(A \\leftarrow B\\) are independent of each other, such as the \"follow\" and \"be followed\" relationships on Weibo or TikTok.

Figure 9-2   Directed and undirected graphs

Graphs can be divided into connected graphs and disconnected graphs based on whether all vertices are connected, as shown in Figure 9-3.

  • For connected graphs, starting from any vertex, all other vertices can be reached.
  • For disconnected graphs, starting from a certain vertex, at least one vertex cannot be reached.

Figure 9-3   Connected and disconnected graphs

We can also add a \"weight\" variable to edges, resulting in weighted graphs as shown in Figure 9-4. For example, in mobile games like \"Honor of Kings\", the system calculates the \"intimacy\" between players based on their shared game time, and such intimacy networks can be represented using weighted graphs.

Figure 9-4   Weighted and unweighted graphs

Graph data structures include the following commonly used terms.

  • Adjacency: When two vertices are connected by an edge, these two vertices are said to be \"adjacent\". In Figure 9-4, the adjacent vertices of vertex 1 are vertices 2, 3, and 5.
  • Path: The sequence of edges from vertex A to vertex B is called a \"path\" from A to B. In Figure 9-4, the edge sequence 1-5-2-4 is a path from vertex 1 to vertex 4.
  • Degree: The number of edges a vertex has. For directed graphs, in-degree indicates how many edges point to the vertex, and out-degree indicates how many edges point out from the vertex.
","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph/#912-representation-of-graphs","level":2,"title":"9.1.2   Representation of Graphs","text":"

Common representations of graphs include \"adjacency matrices\" and \"adjacency lists\". The following uses undirected graphs as examples.

","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph/#1-adjacency-matrix","level":3,"title":"1.   Adjacency Matrix","text":"

Given a graph with \\(n\\) vertices, an adjacency matrix uses an \\(n \\times n\\) matrix to represent the graph, where each row (column) represents a vertex, and matrix elements represent edges, using \\(1\\) or \\(0\\) to indicate whether an edge exists between two vertices.

As shown in Figure 9-5, let the adjacency matrix be \\(M\\) and the vertex list be \\(V\\). Then matrix element \\(M[i, j] = 1\\) indicates that an edge exists between vertex \\(V[i]\\) and vertex \\(V[j]\\), whereas \\(M[i, j] = 0\\) indicates no edge between the two vertices.

Figure 9-5   Adjacency matrix representation of a graph

Adjacency matrices have the following properties.

  • In simple graphs, vertices cannot connect to themselves, so the elements on the main diagonal of the adjacency matrix are meaningless.
  • For undirected graphs, edges in both directions are equivalent, so the adjacency matrix is symmetric about the main diagonal.
  • Replacing the elements of the adjacency matrix from \\(1\\) and \\(0\\) to weights allows representation of weighted graphs.

When using adjacency matrices to represent graphs, we can directly access matrix elements to obtain edges, resulting in highly efficient addition, deletion, lookup, and modification operations, all with a time complexity of \\(O(1)\\). However, the space complexity of the matrix is \\(O(n^2)\\), which consumes significant memory.

","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph/#2-adjacency-list","level":3,"title":"2.   Adjacency List","text":"

An adjacency list uses \\(n\\) linked lists to represent a graph, with linked list nodes representing vertices. The \\(i\\)-th linked list corresponds to vertex \\(i\\) and stores all adjacent vertices of that vertex (vertices connected to that vertex). Figure 9-6 shows an example of a graph stored using an adjacency list.

Figure 9-6   Adjacency list representation of a graph

Adjacency lists only store edges that actually exist, and the total number of edges is typically much less than \\(n^2\\), making them more space-efficient. However, finding edges in an adjacency list requires traversing the linked list, so its time efficiency is inferior to that of adjacency matrices.

Observing Figure 9-6, the structure of adjacency lists is very similar to \"chaining\" in hash tables, so we can adopt similar methods to optimize efficiency. For example, when linked lists are long, they can be converted to AVL trees or red-black trees, thereby optimizing time efficiency from \\(O(n)\\) to \\(O(\\log n)\\); linked lists can also be converted to hash tables, thereby reducing time complexity to \\(O(1)\\).

","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph/#913-common-applications-of-graphs","level":2,"title":"9.1.3   Common Applications of Graphs","text":"

As shown in Table 9-1, many real-world systems can be modeled using graphs, and corresponding problems can be reduced to graph computation problems.

Table 9-1   Common graphs in real life

Vertices Edges Graph Computation Problem Social network Users Friend relationships Potential friend recommendation Subway lines Stations Connectivity between stations Shortest route recommendation Solar system Celestial bodies Gravitational forces between celestial bodies Planetary orbit calculation","path":["Chapter 9. Graph","9.1   Graph"],"tags":[]},{"location":"chapter_graph/graph_operations/","level":1,"title":"9.2   Basic Operations on Graphs","text":"

Basic operations on graphs can be divided into operations on \"edges\" and operations on \"vertices\". Under the two representation methods of \"adjacency matrix\" and \"adjacency list\", the implementation methods differ.

","path":["Chapter 9. Graph","9.2   Basic Operations on Graphs"],"tags":[]},{"location":"chapter_graph/graph_operations/#921-implementation-based-on-adjacency-matrix","level":2,"title":"9.2.1   Implementation Based on Adjacency Matrix","text":"

Given an undirected graph with \\(n\\) vertices, the various operations are implemented as shown in Figure 9-7.

  • Adding or removing an edge: Directly modify the specified edge in the adjacency matrix, using \\(O(1)\\) time. Since it is an undirected graph, both directions of the edge need to be updated simultaneously.
  • Adding a vertex: Add a row and a column at the end of the adjacency matrix and fill them all with \\(0\\)s, using \\(O(n)\\) time.
  • Removing a vertex: Delete a row and a column in the adjacency matrix. The worst case occurs when removing the first row and column, requiring \\((n-1)^2\\) elements to be \"moved up and to the left\", thus using \\(O(n^2)\\) time.
  • Initialization: Pass in \\(n\\) vertices, initialize a vertex list vertices of length \\(n\\), using \\(O(n)\\) time; initialize an adjacency matrix adjMat of size \\(n \\times n\\), using \\(O(n^2)\\) time.
Initialize adjacency matrixAdd an edgeRemove an edgeAdd a vertexRemove a vertex

Figure 9-7   Initialization, adding and removing edges, adding and removing vertices in adjacency matrix

The following is the implementation code for graphs represented using an adjacency matrix:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_adjacency_matrix.py
class GraphAdjMat:\n    \"\"\"Undirected graph class based on adjacency matrix\"\"\"\n\n    def __init__(self, vertices: list[int], edges: list[list[int]]):\n        \"\"\"Constructor\"\"\"\n        # Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n        self.vertices: list[int] = []\n        # Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n        self.adj_mat: list[list[int]] = []\n        # Add vertices\n        for val in vertices:\n            self.add_vertex(val)\n        # Add edges\n        # Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for e in edges:\n            self.add_edge(e[0], e[1])\n\n    def size(self) -> int:\n        \"\"\"Get the number of vertices\"\"\"\n        return len(self.vertices)\n\n    def add_vertex(self, val: int):\n        \"\"\"Add vertex\"\"\"\n        n = self.size()\n        # Add the value of the new vertex to the vertex list\n        self.vertices.append(val)\n        # Add a row to the adjacency matrix\n        new_row = [0] * n\n        self.adj_mat.append(new_row)\n        # Add a column to the adjacency matrix\n        for row in self.adj_mat:\n            row.append(0)\n\n    def remove_vertex(self, index: int):\n        \"\"\"Remove vertex\"\"\"\n        if index >= self.size():\n            raise IndexError()\n        # Remove the vertex at index from the vertex list\n        self.vertices.pop(index)\n        # Remove the row at index from the adjacency matrix\n        self.adj_mat.pop(index)\n        # Remove the column at index from the adjacency matrix\n        for row in self.adj_mat:\n            row.pop(index)\n\n    def add_edge(self, i: int, j: int):\n        \"\"\"Add edge\"\"\"\n        # Parameters i, j correspond to the vertices element indices\n        # Handle index out of bounds and equality\n        if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:\n            raise IndexError()\n        # In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        self.adj_mat[i][j] = 1\n        self.adj_mat[j][i] = 1\n\n    def remove_edge(self, i: int, j: int):\n        \"\"\"Remove edge\"\"\"\n        # Parameters i, j correspond to the vertices element indices\n        # Handle index out of bounds and equality\n        if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:\n            raise IndexError()\n        self.adj_mat[i][j] = 0\n        self.adj_mat[j][i] = 0\n\n    def print(self):\n        \"\"\"Print adjacency matrix\"\"\"\n        print(\"Vertex list =\", self.vertices)\n        print(\"Adjacency matrix =\")\n        print_matrix(self.adj_mat)\n
graph_adjacency_matrix.cpp
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    vector<int> vertices;       // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    vector<vector<int>> adjMat; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n  public:\n    /* Constructor */\n    GraphAdjMat(const vector<int> &vertices, const vector<vector<int>> &edges) {\n        // Add vertex\n        for (int val : vertices) {\n            addVertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for (const vector<int> &edge : edges) {\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int size() const {\n        return vertices.size();\n    }\n\n    /* Add vertex */\n    void addVertex(int val) {\n        int n = size();\n        // Add the value of the new vertex to the vertex list\n        vertices.push_back(val);\n        // Add a row to the adjacency matrix\n        adjMat.emplace_back(vector<int>(n, 0));\n        // Add a column to the adjacency matrix\n        for (vector<int> &row : adjMat) {\n            row.push_back(0);\n        }\n    }\n\n    /* Remove vertex */\n    void removeVertex(int index) {\n        if (index >= size()) {\n            throw out_of_range(\"Vertex does not exist\");\n        }\n        // Remove the vertex at index from the vertex list\n        vertices.erase(vertices.begin() + index);\n        // Remove the row at index from the adjacency matrix\n        adjMat.erase(adjMat.begin() + index);\n        // Remove the column at index from the adjacency matrix\n        for (vector<int> &row : adjMat) {\n            row.erase(row.begin() + index);\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    void addEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n            throw out_of_range(\"Vertex does not exist\");\n        }\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        adjMat[i][j] = 1;\n        adjMat[j][i] = 1;\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    void removeEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n            throw out_of_range(\"Vertex does not exist\");\n        }\n        adjMat[i][j] = 0;\n        adjMat[j][i] = 0;\n    }\n\n    /* Print adjacency matrix */\n    void print() {\n        cout << \"Vertex list = \";\n        printVector(vertices);\n        cout << \"Adjacency matrix =\" << endl;\n        printVectorMatrix(adjMat);\n    }\n};\n
graph_adjacency_matrix.java
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    List<Integer> vertices; // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    List<List<Integer>> adjMat; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    public GraphAdjMat(int[] vertices, int[][] edges) {\n        this.vertices = new ArrayList<>();\n        this.adjMat = new ArrayList<>();\n        // Add vertex\n        for (int val : vertices) {\n            addVertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for (int[] e : edges) {\n            addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    public int size() {\n        return vertices.size();\n    }\n\n    /* Add vertex */\n    public void addVertex(int val) {\n        int n = size();\n        // Add the value of the new vertex to the vertex list\n        vertices.add(val);\n        // Add a row to the adjacency matrix\n        List<Integer> newRow = new ArrayList<>(n);\n        for (int j = 0; j < n; j++) {\n            newRow.add(0);\n        }\n        adjMat.add(newRow);\n        // Add a column to the adjacency matrix\n        for (List<Integer> row : adjMat) {\n            row.add(0);\n        }\n    }\n\n    /* Remove vertex */\n    public void removeVertex(int index) {\n        if (index >= size())\n            throw new IndexOutOfBoundsException();\n        // Remove the vertex at index from the vertex list\n        vertices.remove(index);\n        // Remove the row at index from the adjacency matrix\n        adjMat.remove(index);\n        // Remove the column at index from the adjacency matrix\n        for (List<Integer> row : adjMat) {\n            row.remove(index);\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    public void addEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw new IndexOutOfBoundsException();\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        adjMat.get(i).set(j, 1);\n        adjMat.get(j).set(i, 1);\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    public void removeEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw new IndexOutOfBoundsException();\n        adjMat.get(i).set(j, 0);\n        adjMat.get(j).set(i, 0);\n    }\n\n    /* Print adjacency matrix */\n    public void print() {\n        System.out.print(\"Vertex list = \");\n        System.out.println(vertices);\n        System.out.println(\"Adjacency matrix =\");\n        PrintUtil.printMatrix(adjMat);\n    }\n}\n
graph_adjacency_matrix.cs
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    List<int> vertices;     // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    List<List<int>> adjMat; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    public GraphAdjMat(int[] vertices, int[][] edges) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add vertex\n        foreach (int val in vertices) {\n            AddVertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        foreach (int[] e in edges) {\n            AddEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int Size() {\n        return vertices.Count;\n    }\n\n    /* Add vertex */\n    public void AddVertex(int val) {\n        int n = Size();\n        // Add the value of the new vertex to the vertex list\n        vertices.Add(val);\n        // Add a row to the adjacency matrix\n        List<int> newRow = new(n);\n        for (int j = 0; j < n; j++) {\n            newRow.Add(0);\n        }\n        adjMat.Add(newRow);\n        // Add a column to the adjacency matrix\n        foreach (List<int> row in adjMat) {\n            row.Add(0);\n        }\n    }\n\n    /* Remove vertex */\n    public void RemoveVertex(int index) {\n        if (index >= Size())\n            throw new IndexOutOfRangeException();\n        // Remove the vertex at index from the vertex list\n        vertices.RemoveAt(index);\n        // Remove the row at index from the adjacency matrix\n        adjMat.RemoveAt(index);\n        // Remove the column at index from the adjacency matrix\n        foreach (List<int> row in adjMat) {\n            row.RemoveAt(index);\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    public void AddEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j)\n            throw new IndexOutOfRangeException();\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        adjMat[i][j] = 1;\n        adjMat[j][i] = 1;\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    public void RemoveEdge(int i, int j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j)\n            throw new IndexOutOfRangeException();\n        adjMat[i][j] = 0;\n        adjMat[j][i] = 0;\n    }\n\n    /* Print adjacency matrix */\n    public void Print() {\n        Console.Write(\"Vertex list = \");\n        PrintUtil.PrintList(vertices);\n        Console.WriteLine(\"Adjacency matrix =\");\n        PrintUtil.PrintMatrix(adjMat);\n    }\n}\n
graph_adjacency_matrix.go
/* Undirected graph class based on adjacency matrix */\ntype graphAdjMat struct {\n    // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    vertices []int\n    // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n    adjMat [][]int\n}\n\n/* Constructor */\nfunc newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat {\n    // Add vertex\n    n := len(vertices)\n    adjMat := make([][]int, n)\n    for i := range adjMat {\n        adjMat[i] = make([]int, n)\n    }\n    // Initialize graph\n    g := &graphAdjMat{\n        vertices: vertices,\n        adjMat:   adjMat,\n    }\n    // Add edge\n    // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n    for i := range edges {\n        g.addEdge(edges[i][0], edges[i][1])\n    }\n    return g\n}\n\n/* Get the number of vertices */\nfunc (g *graphAdjMat) size() int {\n    return len(g.vertices)\n}\n\n/* Add vertex */\nfunc (g *graphAdjMat) addVertex(val int) {\n    n := g.size()\n    // Add the value of the new vertex to the vertex list\n    g.vertices = append(g.vertices, val)\n    // Add a row to the adjacency matrix\n    newRow := make([]int, n)\n    g.adjMat = append(g.adjMat, newRow)\n    // Add a column to the adjacency matrix\n    for i := range g.adjMat {\n        g.adjMat[i] = append(g.adjMat[i], 0)\n    }\n}\n\n/* Remove vertex */\nfunc (g *graphAdjMat) removeVertex(index int) {\n    if index >= g.size() {\n        return\n    }\n    // Remove the vertex at index from the vertex list\n    g.vertices = append(g.vertices[:index], g.vertices[index+1:]...)\n    // Remove the row at index from the adjacency matrix\n    g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...)\n    // Remove the column at index from the adjacency matrix\n    for i := range g.adjMat {\n        g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...)\n    }\n}\n\n/* Add edge */\n// Parameters i, j correspond to the vertices element indices\nfunc (g *graphAdjMat) addEdge(i, j int) {\n    // Handle index out of bounds and equality\n    if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {\n        fmt.Errorf(\"%s\", \"Index Out Of Bounds Exception\")\n    }\n    // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n    g.adjMat[i][j] = 1\n    g.adjMat[j][i] = 1\n}\n\n/* Remove edge */\n// Parameters i, j correspond to the vertices element indices\nfunc (g *graphAdjMat) removeEdge(i, j int) {\n    // Handle index out of bounds and equality\n    if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {\n        fmt.Errorf(\"%s\", \"Index Out Of Bounds Exception\")\n    }\n    g.adjMat[i][j] = 0\n    g.adjMat[j][i] = 0\n}\n\n/* Print adjacency matrix */\nfunc (g *graphAdjMat) print() {\n    fmt.Printf(\"\\tVertex list = %v\\n\", g.vertices)\n    fmt.Printf(\"\\tAdjacency matrix = \\n\")\n    for i := range g.adjMat {\n        fmt.Printf(\"\\t\\t\\t%v\\n\", g.adjMat[i])\n    }\n}\n
graph_adjacency_matrix.swift
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    private var vertices: [Int] // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    private var adjMat: [[Int]] // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    init(vertices: [Int], edges: [[Int]]) {\n        self.vertices = []\n        adjMat = []\n        // Add vertex\n        for val in vertices {\n            addVertex(val: val)\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for e in edges {\n            addEdge(i: e[0], j: e[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    func size() -> Int {\n        vertices.count\n    }\n\n    /* Add vertex */\n    func addVertex(val: Int) {\n        let n = size()\n        // Add the value of the new vertex to the vertex list\n        vertices.append(val)\n        // Add a row to the adjacency matrix\n        let newRow = Array(repeating: 0, count: n)\n        adjMat.append(newRow)\n        // Add a column to the adjacency matrix\n        for i in adjMat.indices {\n            adjMat[i].append(0)\n        }\n    }\n\n    /* Remove vertex */\n    func removeVertex(index: Int) {\n        if index >= size() {\n            fatalError(\"Out of bounds\")\n        }\n        // Remove the vertex at index from the vertex list\n        vertices.remove(at: index)\n        // Remove the row at index from the adjacency matrix\n        adjMat.remove(at: index)\n        // Remove the column at index from the adjacency matrix\n        for i in adjMat.indices {\n            adjMat[i].remove(at: index)\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    func addEdge(i: Int, j: Int) {\n        // Handle index out of bounds and equality\n        if i < 0 || j < 0 || i >= size() || j >= size() || i == j {\n            fatalError(\"Out of bounds\")\n        }\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        adjMat[i][j] = 1\n        adjMat[j][i] = 1\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    func removeEdge(i: Int, j: Int) {\n        // Handle index out of bounds and equality\n        if i < 0 || j < 0 || i >= size() || j >= size() || i == j {\n            fatalError(\"Out of bounds\")\n        }\n        adjMat[i][j] = 0\n        adjMat[j][i] = 0\n    }\n\n    /* Print adjacency matrix */\n    func print() {\n        Swift.print(\"Vertex list = \", terminator: \"\")\n        Swift.print(vertices)\n        Swift.print(\"Adjacency matrix =\")\n        PrintUtil.printMatrix(matrix: adjMat)\n    }\n}\n
graph_adjacency_matrix.js
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    vertices; // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    adjMat; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    constructor(vertices, edges) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add vertex\n        for (const val of vertices) {\n            this.addVertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for (const e of edges) {\n            this.addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size() {\n        return this.vertices.length;\n    }\n\n    /* Add vertex */\n    addVertex(val) {\n        const n = this.size();\n        // Add the value of the new vertex to the vertex list\n        this.vertices.push(val);\n        // Add a row to the adjacency matrix\n        const newRow = [];\n        for (let j = 0; j < n; j++) {\n            newRow.push(0);\n        }\n        this.adjMat.push(newRow);\n        // Add a column to the adjacency matrix\n        for (const row of this.adjMat) {\n            row.push(0);\n        }\n    }\n\n    /* Remove vertex */\n    removeVertex(index) {\n        if (index >= this.size()) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        // Remove the vertex at index from the vertex list\n        this.vertices.splice(index, 1);\n\n        // Remove the row at index from the adjacency matrix\n        this.adjMat.splice(index, 1);\n        // Remove the column at index from the adjacency matrix\n        for (const row of this.adjMat) {\n            row.splice(index, 1);\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    addEdge(i, j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        // In undirected graph, adjacency matrix is symmetric about main diagonal, i.e., satisfies (i, j) === (j, i)\n        this.adjMat[i][j] = 1;\n        this.adjMat[j][i] = 1;\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    removeEdge(i, j) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        this.adjMat[i][j] = 0;\n        this.adjMat[j][i] = 0;\n    }\n\n    /* Print adjacency matrix */\n    print() {\n        console.log('Vertex list = ', this.vertices);\n        console.log('Adjacency matrix =', this.adjMat);\n    }\n}\n
graph_adjacency_matrix.ts
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n    vertices: number[]; // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    adjMat: number[][]; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    constructor(vertices: number[], edges: number[][]) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add vertex\n        for (const val of vertices) {\n            this.addVertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for (const e of edges) {\n            this.addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size(): number {\n        return this.vertices.length;\n    }\n\n    /* Add vertex */\n    addVertex(val: number): void {\n        const n: number = this.size();\n        // Add the value of the new vertex to the vertex list\n        this.vertices.push(val);\n        // Add a row to the adjacency matrix\n        const newRow: number[] = [];\n        for (let j: number = 0; j < n; j++) {\n            newRow.push(0);\n        }\n        this.adjMat.push(newRow);\n        // Add a column to the adjacency matrix\n        for (const row of this.adjMat) {\n            row.push(0);\n        }\n    }\n\n    /* Remove vertex */\n    removeVertex(index: number): void {\n        if (index >= this.size()) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        // Remove the vertex at index from the vertex list\n        this.vertices.splice(index, 1);\n\n        // Remove the row at index from the adjacency matrix\n        this.adjMat.splice(index, 1);\n        // Remove the column at index from the adjacency matrix\n        for (const row of this.adjMat) {\n            row.splice(index, 1);\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    addEdge(i: number, j: number): void {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        // In undirected graph, adjacency matrix is symmetric about main diagonal, i.e., satisfies (i, j) === (j, i)\n        this.adjMat[i][j] = 1;\n        this.adjMat[j][i] = 1;\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    removeEdge(i: number, j: number): void {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index Out Of Bounds Exception');\n        }\n        this.adjMat[i][j] = 0;\n        this.adjMat[j][i] = 0;\n    }\n\n    /* Print adjacency matrix */\n    print(): void {\n        console.log('Vertex list = ', this.vertices);\n        console.log('Adjacency matrix =', this.adjMat);\n    }\n}\n
graph_adjacency_matrix.dart
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat {\n  List<int> vertices = []; // Vertex elements, elements represent \"vertex values\", indices represent \"vertex indices\"\n  List<List<int>> adjMat = []; // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n  /* Constructor */\n  GraphAdjMat(List<int> vertices, List<List<int>> edges) {\n    this.vertices = [];\n    this.adjMat = [];\n    // Add vertex\n    for (int val in vertices) {\n      addVertex(val);\n    }\n    // Add edge\n    // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n    for (List<int> e in edges) {\n      addEdge(e[0], e[1]);\n    }\n  }\n\n  /* Get the number of vertices */\n  int size() {\n    return vertices.length;\n  }\n\n  /* Add vertex */\n  void addVertex(int val) {\n    int n = size();\n    // Add the value of the new vertex to the vertex list\n    vertices.add(val);\n    // Add a row to the adjacency matrix\n    List<int> newRow = List.filled(n, 0, growable: true);\n    adjMat.add(newRow);\n    // Add a column to the adjacency matrix\n    for (List<int> row in adjMat) {\n      row.add(0);\n    }\n  }\n\n  /* Remove vertex */\n  void removeVertex(int index) {\n    if (index >= size()) {\n      throw IndexError;\n    }\n    // Remove the vertex at index from the vertex list\n    vertices.removeAt(index);\n    // Remove the row at index from the adjacency matrix\n    adjMat.removeAt(index);\n    // Remove the column at index from the adjacency matrix\n    for (List<int> row in adjMat) {\n      row.removeAt(index);\n    }\n  }\n\n  /* Add edge */\n  // Parameters i, j correspond to the vertices element indices\n  void addEdge(int i, int j) {\n    // Handle index out of bounds and equality\n    if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n      throw IndexError;\n    }\n    // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n    adjMat[i][j] = 1;\n    adjMat[j][i] = 1;\n  }\n\n  /* Remove edge */\n  // Parameters i, j correspond to the vertices element indices\n  void removeEdge(int i, int j) {\n    // Handle index out of bounds and equality\n    if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n      throw IndexError;\n    }\n    adjMat[i][j] = 0;\n    adjMat[j][i] = 0;\n  }\n\n  /* Print adjacency matrix */\n  void printAdjMat() {\n    print(\"Vertex list = $vertices\");\n    print(\"Adjacency matrix = \");\n    printMatrix(adjMat);\n  }\n}\n
graph_adjacency_matrix.rs
/* Undirected graph type based on adjacency matrix */\npub struct GraphAdjMat {\n    // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    pub vertices: Vec<i32>,\n    // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n    pub adj_mat: Vec<Vec<i32>>,\n}\n\nimpl GraphAdjMat {\n    /* Constructor */\n    pub fn new(vertices: Vec<i32>, edges: Vec<[usize; 2]>) -> Self {\n        let mut graph = GraphAdjMat {\n            vertices: vec![],\n            adj_mat: vec![],\n        };\n        // Add vertex\n        for val in vertices {\n            graph.add_vertex(val);\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for edge in edges {\n            graph.add_edge(edge[0], edge[1])\n        }\n\n        graph\n    }\n\n    /* Get the number of vertices */\n    pub fn size(&self) -> usize {\n        self.vertices.len()\n    }\n\n    /* Add vertex */\n    pub fn add_vertex(&mut self, val: i32) {\n        let n = self.size();\n        // Add the value of the new vertex to the vertex list\n        self.vertices.push(val);\n        // Add a row to the adjacency matrix\n        self.adj_mat.push(vec![0; n]);\n        // Add a column to the adjacency matrix\n        for row in self.adj_mat.iter_mut() {\n            row.push(0);\n        }\n    }\n\n    /* Remove vertex */\n    pub fn remove_vertex(&mut self, index: usize) {\n        if index >= self.size() {\n            panic!(\"index error\")\n        }\n        // Remove the vertex at index from the vertex list\n        self.vertices.remove(index);\n        // Remove the row at index from the adjacency matrix\n        self.adj_mat.remove(index);\n        // Remove the column at index from the adjacency matrix\n        for row in self.adj_mat.iter_mut() {\n            row.remove(index);\n        }\n    }\n\n    /* Add edge */\n    pub fn add_edge(&mut self, i: usize, j: usize) {\n        // Parameters i, j correspond to the vertices element indices\n        // Handle index out of bounds and equality\n        if i >= self.size() || j >= self.size() || i == j {\n            panic!(\"index error\")\n        }\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        self.adj_mat[i][j] = 1;\n        self.adj_mat[j][i] = 1;\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    pub fn remove_edge(&mut self, i: usize, j: usize) {\n        // Parameters i, j correspond to the vertices element indices\n        // Handle index out of bounds and equality\n        if i >= self.size() || j >= self.size() || i == j {\n            panic!(\"index error\")\n        }\n        self.adj_mat[i][j] = 0;\n        self.adj_mat[j][i] = 0;\n    }\n\n    /* Print adjacency matrix */\n    pub fn print(&self) {\n        println!(\"Vertex list = {:?}\", self.vertices);\n        println!(\"Adjacency matrix =\");\n        println!(\"[\");\n        for row in &self.adj_mat {\n            println!(\"  {:?},\", row);\n        }\n        println!(\"]\")\n    }\n}\n
graph_adjacency_matrix.c
/* Undirected graph structure based on adjacency matrix */\ntypedef struct {\n    int vertices[MAX_SIZE];\n    int adjMat[MAX_SIZE][MAX_SIZE];\n    int size;\n} GraphAdjMat;\n\n/* Constructor */\nGraphAdjMat *newGraphAdjMat() {\n    GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat));\n    graph->size = 0;\n    for (int i = 0; i < MAX_SIZE; i++) {\n        for (int j = 0; j < MAX_SIZE; j++) {\n            graph->adjMat[i][j] = 0;\n        }\n    }\n    return graph;\n}\n\n/* Destructor */\nvoid delGraphAdjMat(GraphAdjMat *graph) {\n    free(graph);\n}\n\n/* Add vertex */\nvoid addVertex(GraphAdjMat *graph, int val) {\n    if (graph->size == MAX_SIZE) {\n        fprintf(stderr, \"Graph vertex count has reached maximum\\n\");\n        return;\n    }\n    // Add nth vertex and zero nth row and column\n    int n = graph->size;\n    graph->vertices[n] = val;\n    for (int i = 0; i <= n; i++) {\n        graph->adjMat[n][i] = graph->adjMat[i][n] = 0;\n    }\n    graph->size++;\n}\n\n/* Remove vertex */\nvoid removeVertex(GraphAdjMat *graph, int index) {\n    if (index < 0 || index >= graph->size) {\n        fprintf(stderr, \"Vertex index out of bounds\\n\");\n        return;\n    }\n    // Remove the vertex at index from the vertex list\n    for (int i = index; i < graph->size - 1; i++) {\n        graph->vertices[i] = graph->vertices[i + 1];\n    }\n    // Remove the row at index from the adjacency matrix\n    for (int i = index; i < graph->size - 1; i++) {\n        for (int j = 0; j < graph->size; j++) {\n            graph->adjMat[i][j] = graph->adjMat[i + 1][j];\n        }\n    }\n    // Remove the column at index from the adjacency matrix\n    for (int i = 0; i < graph->size; i++) {\n        for (int j = index; j < graph->size - 1; j++) {\n            graph->adjMat[i][j] = graph->adjMat[i][j + 1];\n        }\n    }\n    graph->size--;\n}\n\n/* Add edge */\n// Parameters i, j correspond to the vertices element indices\nvoid addEdge(GraphAdjMat *graph, int i, int j) {\n    if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) {\n        fprintf(stderr, \"Edge index out of bounds or equal\\n\");\n        return;\n    }\n    graph->adjMat[i][j] = 1;\n    graph->adjMat[j][i] = 1;\n}\n\n/* Remove edge */\n// Parameters i, j correspond to the vertices element indices\nvoid removeEdge(GraphAdjMat *graph, int i, int j) {\n    if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) {\n        fprintf(stderr, \"Edge index out of bounds or equal\\n\");\n        return;\n    }\n    graph->adjMat[i][j] = 0;\n    graph->adjMat[j][i] = 0;\n}\n\n/* Print adjacency matrix */\nvoid printGraphAdjMat(GraphAdjMat *graph) {\n    printf(\"Vertex list = \");\n    printArray(graph->vertices, graph->size);\n    printf(\"Adjacency matrix =\\n\");\n    for (int i = 0; i < graph->size; i++) {\n        printArray(graph->adjMat[i], graph->size);\n    }\n}\n
graph_adjacency_matrix.kt
/* Undirected graph class based on adjacency matrix */\nclass GraphAdjMat(vertices: IntArray, edges: Array<IntArray>) {\n    val vertices = mutableListOf<Int>() // Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    val adjMat = mutableListOf<MutableList<Int>>() // Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n\n    /* Constructor */\n    init {\n        // Add vertex\n        for (vertex in vertices) {\n            addVertex(vertex)\n        }\n        // Add edge\n        // Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n        for (edge in edges) {\n            addEdge(edge[0], edge[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    fun size(): Int {\n        return vertices.size\n    }\n\n    /* Add vertex */\n    fun addVertex(_val: Int) {\n        val n = size()\n        // Add the value of the new vertex to the vertex list\n        vertices.add(_val)\n        // Add a row to the adjacency matrix\n        val newRow = mutableListOf<Int>()\n        for (j in 0..<n) {\n            newRow.add(0)\n        }\n        adjMat.add(newRow)\n        // Add a column to the adjacency matrix\n        for (row in adjMat) {\n            row.add(0)\n        }\n    }\n\n    /* Remove vertex */\n    fun removeVertex(index: Int) {\n        if (index >= size())\n            throw IndexOutOfBoundsException()\n        // Remove the vertex at index from the vertex list\n        vertices.removeAt(index)\n        // Remove the row at index from the adjacency matrix\n        adjMat.removeAt(index)\n        // Remove the column at index from the adjacency matrix\n        for (row in adjMat) {\n            row.removeAt(index)\n        }\n    }\n\n    /* Add edge */\n    // Parameters i, j correspond to the vertices element indices\n    fun addEdge(i: Int, j: Int) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw IndexOutOfBoundsException()\n        // In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n        adjMat[i][j] = 1\n        adjMat[j][i] = 1\n    }\n\n    /* Remove edge */\n    // Parameters i, j correspond to the vertices element indices\n    fun removeEdge(i: Int, j: Int) {\n        // Handle index out of bounds and equality\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw IndexOutOfBoundsException()\n        adjMat[i][j] = 0\n        adjMat[j][i] = 0\n    }\n\n    /* Print adjacency matrix */\n    fun print() {\n        print(\"Vertex list = \")\n        println(vertices)\n        println(\"Adjacency matrix =\")\n        printMatrix(adjMat)\n    }\n}\n
graph_adjacency_matrix.rb
### Undirected graph class based on adjacency matrix ###\nclass GraphAdjMat\n  def initialize(vertices, edges)\n    ### Constructor ###\n    # Vertex list, where the element represents the \"vertex value\" and the index represents the \"vertex index\"\n    @vertices = []\n    # Adjacency matrix, where the row and column indices correspond to the \"vertex index\"\n    @adj_mat = []\n    # Add vertex\n    vertices.each { |val| add_vertex(val) }\n    # Add edge\n    # Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices\n    edges.each { |e| add_edge(e[0], e[1]) }\n  end\n\n  ### Get number of vertices ###\n  def size\n    @vertices.length\n  end\n\n  ### Add vertex ###\n  def add_vertex(val)\n    n = size\n    # Add the value of the new vertex to the vertex list\n    @vertices << val\n    # Add a row to the adjacency matrix\n    new_row = Array.new(n, 0)\n    @adj_mat << new_row\n    # Add a column to the adjacency matrix\n    @adj_mat.each { |row| row << 0 }\n  end\n\n  ### Delete vertex ###\n  def remove_vertex(index)\n    raise IndexError if index >= size\n\n    # Remove the vertex at index from the vertex list\n    @vertices.delete_at(index)\n    # Remove the row at index from the adjacency matrix\n    @adj_mat.delete_at(index)\n    # Remove the column at index from the adjacency matrix\n    @adj_mat.each { |row| row.delete_at(index) }\n  end\n\n  ### Add edge ###\n  def add_edge(i, j)\n    # Parameters i, j correspond to the vertices element indices\n    # Handle index out of bounds and equality\n    if i < 0 || j < 0 || i >= size || j >= size || i == j\n      raise IndexError\n    end\n    # In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)\n    @adj_mat[i][j] = 1\n    @adj_mat[j][i] = 1\n  end\n\n  ### Delete edge ###\n  def remove_edge(i, j)\n    # Parameters i, j correspond to the vertices element indices\n    # Handle index out of bounds and equality\n    if i < 0 || j < 0 || i >= size || j >= size || i == j\n      raise IndexError\n    end\n    @adj_mat[i][j] = 0\n    @adj_mat[j][i] = 0\n  end\n\n  ### Print adjacency matrix ###\n  def __print__\n    puts \"Vertex list = #{@vertices}\"\n    puts 'Adjacency matrix ='\n    print_matrix(@adj_mat)\n  end\nend\n
","path":["Chapter 9. Graph","9.2   Basic Operations on Graphs"],"tags":[]},{"location":"chapter_graph/graph_operations/#922-implementation-based-on-adjacency-list","level":2,"title":"9.2.2   Implementation Based on Adjacency List","text":"

Given an undirected graph with a total of \\(n\\) vertices and \\(m\\) edges, the various operations can be implemented as shown in Figure 9-8.

  • Adding an edge: Add the edge at the end of the corresponding vertex's linked list, using \\(O(1)\\) time. Since it is an undirected graph, edges in both directions need to be added simultaneously.
  • Removing an edge: Find and remove the specified edge in the corresponding vertex's linked list, using \\(O(m)\\) time. In an undirected graph, edges in both directions need to be removed simultaneously.
  • Adding a vertex: Add a linked list in the adjacency list and set the new vertex as the head node of the list, using \\(O(1)\\) time.
  • Removing a vertex: Traverse the entire adjacency list and remove all edges containing the specified vertex, using \\(O(n + m)\\) time.
  • Initialization: Create \\(n\\) vertices and \\(2m\\) edges in the adjacency list, using \\(O(n + m)\\) time.
Initialize adjacency listAdd an edgeRemove an edgeAdd a vertexRemove a vertex

Figure 9-8   Initialization, adding and removing edges, adding and removing vertices in adjacency list

The following is the adjacency list code implementation. Compared to Figure 9-8, the actual code has the following differences.

  • For convenience in adding and removing vertices, and to simplify the code, we use lists (dynamic arrays) instead of linked lists.
  • A hash table is used to store the adjacency list, where key is the vertex instance and value is the list (linked list) of adjacent vertices for that vertex.

Additionally, we use the Vertex class to represent vertices in the adjacency list. The reason for this is: if we used list indices to distinguish different vertices as with adjacency matrices, then to delete the vertex at index \\(i\\), we would need to traverse the entire adjacency list and decrement all indices greater than \\(i\\) by \\(1\\), which is very inefficient. However, if each vertex is a unique Vertex instance, deleting a vertex does not require modifying other vertices.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_adjacency_list.py
class GraphAdjList:\n    \"\"\"Undirected graph class based on adjacency list\"\"\"\n\n    def __init__(self, edges: list[list[Vertex]]):\n        \"\"\"Constructor\"\"\"\n        # Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n        self.adj_list = dict[Vertex, list[Vertex]]()\n        # Add all vertices and edges\n        for edge in edges:\n            self.add_vertex(edge[0])\n            self.add_vertex(edge[1])\n            self.add_edge(edge[0], edge[1])\n\n    def size(self) -> int:\n        \"\"\"Get the number of vertices\"\"\"\n        return len(self.adj_list)\n\n    def add_edge(self, vet1: Vertex, vet2: Vertex):\n        \"\"\"Add edge\"\"\"\n        if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:\n            raise ValueError()\n        # Add edge vet1 - vet2\n        self.adj_list[vet1].append(vet2)\n        self.adj_list[vet2].append(vet1)\n\n    def remove_edge(self, vet1: Vertex, vet2: Vertex):\n        \"\"\"Remove edge\"\"\"\n        if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:\n            raise ValueError()\n        # Remove edge vet1 - vet2\n        self.adj_list[vet1].remove(vet2)\n        self.adj_list[vet2].remove(vet1)\n\n    def add_vertex(self, vet: Vertex):\n        \"\"\"Add vertex\"\"\"\n        if vet in self.adj_list:\n            return\n        # Add a new linked list in the adjacency list\n        self.adj_list[vet] = []\n\n    def remove_vertex(self, vet: Vertex):\n        \"\"\"Remove vertex\"\"\"\n        if vet not in self.adj_list:\n            raise ValueError()\n        # Remove the linked list corresponding to vertex vet in the adjacency list\n        self.adj_list.pop(vet)\n        # Traverse the linked lists of other vertices and remove all edges containing vet\n        for vertex in self.adj_list:\n            if vet in self.adj_list[vertex]:\n                self.adj_list[vertex].remove(vet)\n\n    def print(self):\n        \"\"\"Print adjacency list\"\"\"\n        print(\"Adjacency list =\")\n        for vertex in self.adj_list:\n            tmp = [v.val for v in self.adj_list[vertex]]\n            print(f\"{vertex.val}: {tmp},\")\n
graph_adjacency_list.cpp
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n  public:\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    unordered_map<Vertex *, vector<Vertex *>> adjList;\n\n    /* Remove specified node from vector */\n    void remove(vector<Vertex *> &vec, Vertex *vet) {\n        for (int i = 0; i < vec.size(); i++) {\n            if (vec[i] == vet) {\n                vec.erase(vec.begin() + i);\n                break;\n            }\n        }\n    }\n\n    /* Constructor */\n    GraphAdjList(const vector<vector<Vertex *>> &edges) {\n        // Add all vertices and edges\n        for (const vector<Vertex *> &edge : edges) {\n            addVertex(edge[0]);\n            addVertex(edge[1]);\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int size() {\n        return adjList.size();\n    }\n\n    /* Add edge */\n    void addEdge(Vertex *vet1, Vertex *vet2) {\n        if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)\n            throw invalid_argument(\"Vertex does not exist\");\n        // Add edge vet1 - vet2\n        adjList[vet1].push_back(vet2);\n        adjList[vet2].push_back(vet1);\n    }\n\n    /* Remove edge */\n    void removeEdge(Vertex *vet1, Vertex *vet2) {\n        if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)\n            throw invalid_argument(\"Vertex does not exist\");\n        // Remove edge vet1 - vet2\n        remove(adjList[vet1], vet2);\n        remove(adjList[vet2], vet1);\n    }\n\n    /* Add vertex */\n    void addVertex(Vertex *vet) {\n        if (adjList.count(vet))\n            return;\n        // Add a new linked list in the adjacency list\n        adjList[vet] = vector<Vertex *>();\n    }\n\n    /* Remove vertex */\n    void removeVertex(Vertex *vet) {\n        if (!adjList.count(vet))\n            throw invalid_argument(\"Vertex does not exist\");\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        adjList.erase(vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for (auto &adj : adjList) {\n            remove(adj.second, vet);\n        }\n    }\n\n    /* Print adjacency list */\n    void print() {\n        cout << \"Adjacency list =\" << endl;\n        for (auto &adj : adjList) {\n            const auto &key = adj.first;\n            const auto &vec = adj.second;\n            cout << key->val << \": \";\n            printVector(vetsToVals(vec));\n        }\n    }\n};\n
graph_adjacency_list.java
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    Map<Vertex, List<Vertex>> adjList;\n\n    /* Constructor */\n    public GraphAdjList(Vertex[][] edges) {\n        this.adjList = new HashMap<>();\n        // Add all vertices and edges\n        for (Vertex[] edge : edges) {\n            addVertex(edge[0]);\n            addVertex(edge[1]);\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    public int size() {\n        return adjList.size();\n    }\n\n    /* Add edge */\n    public void addEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw new IllegalArgumentException();\n        // Add edge vet1 - vet2\n        adjList.get(vet1).add(vet2);\n        adjList.get(vet2).add(vet1);\n    }\n\n    /* Remove edge */\n    public void removeEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw new IllegalArgumentException();\n        // Remove edge vet1 - vet2\n        adjList.get(vet1).remove(vet2);\n        adjList.get(vet2).remove(vet1);\n    }\n\n    /* Add vertex */\n    public void addVertex(Vertex vet) {\n        if (adjList.containsKey(vet))\n            return;\n        // Add a new linked list in the adjacency list\n        adjList.put(vet, new ArrayList<>());\n    }\n\n    /* Remove vertex */\n    public void removeVertex(Vertex vet) {\n        if (!adjList.containsKey(vet))\n            throw new IllegalArgumentException();\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        adjList.remove(vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for (List<Vertex> list : adjList.values()) {\n            list.remove(vet);\n        }\n    }\n\n    /* Print adjacency list */\n    public void print() {\n        System.out.println(\"Adjacency list =\");\n        for (Map.Entry<Vertex, List<Vertex>> pair : adjList.entrySet()) {\n            List<Integer> tmp = new ArrayList<>();\n            for (Vertex vertex : pair.getValue())\n                tmp.add(vertex.val);\n            System.out.println(pair.getKey().val + \": \" + tmp + \",\");\n        }\n    }\n}\n
graph_adjacency_list.cs
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    public Dictionary<Vertex, List<Vertex>> adjList;\n\n    /* Constructor */\n    public GraphAdjList(Vertex[][] edges) {\n        adjList = [];\n        // Add all vertices and edges\n        foreach (Vertex[] edge in edges) {\n            AddVertex(edge[0]);\n            AddVertex(edge[1]);\n            AddEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int Size() {\n        return adjList.Count;\n    }\n\n    /* Add edge */\n    public void AddEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2)\n            throw new InvalidOperationException();\n        // Add edge vet1 - vet2\n        adjList[vet1].Add(vet2);\n        adjList[vet2].Add(vet1);\n    }\n\n    /* Remove edge */\n    public void RemoveEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2)\n            throw new InvalidOperationException();\n        // Remove edge vet1 - vet2\n        adjList[vet1].Remove(vet2);\n        adjList[vet2].Remove(vet1);\n    }\n\n    /* Add vertex */\n    public void AddVertex(Vertex vet) {\n        if (adjList.ContainsKey(vet))\n            return;\n        // Add a new linked list in the adjacency list\n        adjList.Add(vet, []);\n    }\n\n    /* Remove vertex */\n    public void RemoveVertex(Vertex vet) {\n        if (!adjList.ContainsKey(vet))\n            throw new InvalidOperationException();\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        adjList.Remove(vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        foreach (List<Vertex> list in adjList.Values) {\n            list.Remove(vet);\n        }\n    }\n\n    /* Print adjacency list */\n    public void Print() {\n        Console.WriteLine(\"Adjacency list =\");\n        foreach (KeyValuePair<Vertex, List<Vertex>> pair in adjList) {\n            List<int> tmp = [];\n            foreach (Vertex vertex in pair.Value)\n                tmp.Add(vertex.val);\n            Console.WriteLine(pair.Key.val + \": [\" + string.Join(\", \", tmp) + \"],\");\n        }\n    }\n}\n
graph_adjacency_list.go
/* Undirected graph class based on adjacency list */\ntype graphAdjList struct {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    adjList map[Vertex][]Vertex\n}\n\n/* Constructor */\nfunc newGraphAdjList(edges [][]Vertex) *graphAdjList {\n    g := &graphAdjList{\n        adjList: make(map[Vertex][]Vertex),\n    }\n    // Add all vertices and edges\n    for _, edge := range edges {\n        g.addVertex(edge[0])\n        g.addVertex(edge[1])\n        g.addEdge(edge[0], edge[1])\n    }\n    return g\n}\n\n/* Get the number of vertices */\nfunc (g *graphAdjList) size() int {\n    return len(g.adjList)\n}\n\n/* Add edge */\nfunc (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) {\n    _, ok1 := g.adjList[vet1]\n    _, ok2 := g.adjList[vet2]\n    if !ok1 || !ok2 || vet1 == vet2 {\n        panic(\"error\")\n    }\n    // Add edge vet1 - vet2, add anonymous struct{},\n    g.adjList[vet1] = append(g.adjList[vet1], vet2)\n    g.adjList[vet2] = append(g.adjList[vet2], vet1)\n}\n\n/* Remove edge */\nfunc (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) {\n    _, ok1 := g.adjList[vet1]\n    _, ok2 := g.adjList[vet2]\n    if !ok1 || !ok2 || vet1 == vet2 {\n        panic(\"error\")\n    }\n    // Remove edge vet1 - vet2\n    g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2)\n    g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1)\n}\n\n/* Add vertex */\nfunc (g *graphAdjList) addVertex(vet Vertex) {\n    _, ok := g.adjList[vet]\n    if ok {\n        return\n    }\n    // Add a new linked list in the adjacency list\n    g.adjList[vet] = make([]Vertex, 0)\n}\n\n/* Remove vertex */\nfunc (g *graphAdjList) removeVertex(vet Vertex) {\n    _, ok := g.adjList[vet]\n    if !ok {\n        panic(\"error\")\n    }\n    // Remove the linked list corresponding to vertex vet in the adjacency list\n    delete(g.adjList, vet)\n    // Traverse the linked lists of other vertices and remove all edges containing vet\n    for v, list := range g.adjList {\n        g.adjList[v] = DeleteSliceElms(list, vet)\n    }\n}\n\n/* Print adjacency list */\nfunc (g *graphAdjList) print() {\n    var builder strings.Builder\n    fmt.Printf(\"Adjacency list = \\n\")\n    for k, v := range g.adjList {\n        builder.WriteString(\"\\t\\t\" + strconv.Itoa(k.Val) + \": \")\n        for _, vet := range v {\n            builder.WriteString(strconv.Itoa(vet.Val) + \" \")\n        }\n        fmt.Println(builder.String())\n        builder.Reset()\n    }\n}\n
graph_adjacency_list.swift
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    public private(set) var adjList: [Vertex: [Vertex]]\n\n    /* Constructor */\n    public init(edges: [[Vertex]]) {\n        adjList = [:]\n        // Add all vertices and edges\n        for edge in edges {\n            addVertex(vet: edge[0])\n            addVertex(vet: edge[1])\n            addEdge(vet1: edge[0], vet2: edge[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    public func size() -> Int {\n        adjList.count\n    }\n\n    /* Add edge */\n    public func addEdge(vet1: Vertex, vet2: Vertex) {\n        if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\n            fatalError(\"Invalid parameter\")\n        }\n        // Add edge vet1 - vet2\n        adjList[vet1]?.append(vet2)\n        adjList[vet2]?.append(vet1)\n    }\n\n    /* Remove edge */\n    public func removeEdge(vet1: Vertex, vet2: Vertex) {\n        if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\n            fatalError(\"Invalid parameter\")\n        }\n        // Remove edge vet1 - vet2\n        adjList[vet1]?.removeAll { $0 == vet2 }\n        adjList[vet2]?.removeAll { $0 == vet1 }\n    }\n\n    /* Add vertex */\n    public func addVertex(vet: Vertex) {\n        if adjList[vet] != nil {\n            return\n        }\n        // Add a new linked list in the adjacency list\n        adjList[vet] = []\n    }\n\n    /* Remove vertex */\n    public func removeVertex(vet: Vertex) {\n        if adjList[vet] == nil {\n            fatalError(\"Invalid parameter\")\n        }\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        adjList.removeValue(forKey: vet)\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for key in adjList.keys {\n            adjList[key]?.removeAll { $0 == vet }\n        }\n    }\n\n    /* Print adjacency list */\n    public func print() {\n        Swift.print(\"Adjacency list =\")\n        for (vertex, list) in adjList {\n            let list = list.map { $0.val }\n            Swift.print(\"\\(vertex.val): \\(list),\")\n        }\n    }\n}\n
graph_adjacency_list.js
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    adjList;\n\n    /* Constructor */\n    constructor(edges) {\n        this.adjList = new Map();\n        // Add all vertices and edges\n        for (const edge of edges) {\n            this.addVertex(edge[0]);\n            this.addVertex(edge[1]);\n            this.addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size() {\n        return this.adjList.size;\n    }\n\n    /* Add edge */\n    addEdge(vet1, vet2) {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2\n        ) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Add edge vet1 - vet2\n        this.adjList.get(vet1).push(vet2);\n        this.adjList.get(vet2).push(vet1);\n    }\n\n    /* Remove edge */\n    removeEdge(vet1, vet2) {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2 ||\n            this.adjList.get(vet1).indexOf(vet2) === -1\n        ) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Remove edge vet1 - vet2\n        this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1);\n        this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1);\n    }\n\n    /* Add vertex */\n    addVertex(vet) {\n        if (this.adjList.has(vet)) return;\n        // Add a new linked list in the adjacency list\n        this.adjList.set(vet, []);\n    }\n\n    /* Remove vertex */\n    removeVertex(vet) {\n        if (!this.adjList.has(vet)) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        this.adjList.delete(vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for (const set of this.adjList.values()) {\n            const index = set.indexOf(vet);\n            if (index > -1) {\n                set.splice(index, 1);\n            }\n        }\n    }\n\n    /* Print adjacency list */\n    print() {\n        console.log('Adjacency list =');\n        for (const [key, value] of this.adjList) {\n            const tmp = [];\n            for (const vertex of value) {\n                tmp.push(vertex.val);\n            }\n            console.log(key.val + ': ' + tmp.join());\n        }\n    }\n}\n
graph_adjacency_list.ts
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    adjList: Map<Vertex, Vertex[]>;\n\n    /* Constructor */\n    constructor(edges: Vertex[][]) {\n        this.adjList = new Map();\n        // Add all vertices and edges\n        for (const edge of edges) {\n            this.addVertex(edge[0]);\n            this.addVertex(edge[1]);\n            this.addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size(): number {\n        return this.adjList.size;\n    }\n\n    /* Add edge */\n    addEdge(vet1: Vertex, vet2: Vertex): void {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2\n        ) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Add edge vet1 - vet2\n        this.adjList.get(vet1).push(vet2);\n        this.adjList.get(vet2).push(vet1);\n    }\n\n    /* Remove edge */\n    removeEdge(vet1: Vertex, vet2: Vertex): void {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2 ||\n            this.adjList.get(vet1).indexOf(vet2) === -1\n        ) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Remove edge vet1 - vet2\n        this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1);\n        this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1);\n    }\n\n    /* Add vertex */\n    addVertex(vet: Vertex): void {\n        if (this.adjList.has(vet)) return;\n        // Add a new linked list in the adjacency list\n        this.adjList.set(vet, []);\n    }\n\n    /* Remove vertex */\n    removeVertex(vet: Vertex): void {\n        if (!this.adjList.has(vet)) {\n            throw new Error('Illegal Argument Exception');\n        }\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        this.adjList.delete(vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for (const set of this.adjList.values()) {\n            const index: number = set.indexOf(vet);\n            if (index > -1) {\n                set.splice(index, 1);\n            }\n        }\n    }\n\n    /* Print adjacency list */\n    print(): void {\n        console.log('Adjacency list =');\n        for (const [key, value] of this.adjList.entries()) {\n            const tmp = [];\n            for (const vertex of value) {\n                tmp.push(vertex.val);\n            }\n            console.log(key.val + ': ' + tmp.join());\n        }\n    }\n}\n
graph_adjacency_list.dart
/* Undirected graph class based on adjacency list */\nclass GraphAdjList {\n  // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n  Map<Vertex, List<Vertex>> adjList = {};\n\n  /* Constructor */\n  GraphAdjList(List<List<Vertex>> edges) {\n    for (List<Vertex> edge in edges) {\n      addVertex(edge[0]);\n      addVertex(edge[1]);\n      addEdge(edge[0], edge[1]);\n    }\n  }\n\n  /* Get the number of vertices */\n  int size() {\n    return adjList.length;\n  }\n\n  /* Add edge */\n  void addEdge(Vertex vet1, Vertex vet2) {\n    if (!adjList.containsKey(vet1) ||\n        !adjList.containsKey(vet2) ||\n        vet1 == vet2) {\n      throw ArgumentError;\n    }\n    // Add edge vet1 - vet2\n    adjList[vet1]!.add(vet2);\n    adjList[vet2]!.add(vet1);\n  }\n\n  /* Remove edge */\n  void removeEdge(Vertex vet1, Vertex vet2) {\n    if (!adjList.containsKey(vet1) ||\n        !adjList.containsKey(vet2) ||\n        vet1 == vet2) {\n      throw ArgumentError;\n    }\n    // Remove edge vet1 - vet2\n    adjList[vet1]!.remove(vet2);\n    adjList[vet2]!.remove(vet1);\n  }\n\n  /* Add vertex */\n  void addVertex(Vertex vet) {\n    if (adjList.containsKey(vet)) return;\n    // Add a new linked list in the adjacency list\n    adjList[vet] = [];\n  }\n\n  /* Remove vertex */\n  void removeVertex(Vertex vet) {\n    if (!adjList.containsKey(vet)) {\n      throw ArgumentError;\n    }\n    // Remove the linked list corresponding to vertex vet in the adjacency list\n    adjList.remove(vet);\n    // Traverse the linked lists of other vertices and remove all edges containing vet\n    adjList.forEach((key, value) {\n      value.remove(vet);\n    });\n  }\n\n  /* Print adjacency list */\n  void printAdjList() {\n    print(\"Adjacency list =\");\n    adjList.forEach((key, value) {\n      List<int> tmp = [];\n      for (Vertex vertex in value) {\n        tmp.add(vertex.val);\n      }\n      print(\"${key.val}: $tmp,\");\n    });\n  }\n}\n
graph_adjacency_list.rs
/* Undirected graph type based on adjacency list */\npub struct GraphAdjList {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    pub adj_list: HashMap<Vertex, Vec<Vertex>>, // maybe HashSet<Vertex> for value part is better?\n}\n\nimpl GraphAdjList {\n    /* Constructor */\n    pub fn new(edges: Vec<[Vertex; 2]>) -> Self {\n        let mut graph = GraphAdjList {\n            adj_list: HashMap::new(),\n        };\n        // Add all vertices and edges\n        for edge in edges {\n            graph.add_vertex(edge[0]);\n            graph.add_vertex(edge[1]);\n            graph.add_edge(edge[0], edge[1]);\n        }\n\n        graph\n    }\n\n    /* Get the number of vertices */\n    #[allow(unused)]\n    pub fn size(&self) -> usize {\n        self.adj_list.len()\n    }\n\n    /* Add edge */\n    pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) {\n        if vet1 == vet2 {\n            panic!(\"value error\");\n        }\n        // Add edge vet1 - vet2\n        self.adj_list.entry(vet1).or_default().push(vet2);\n        self.adj_list.entry(vet2).or_default().push(vet1);\n    }\n\n    /* Remove edge */\n    #[allow(unused)]\n    pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) {\n        if vet1 == vet2 {\n            panic!(\"value error\");\n        }\n        // Remove edge vet1 - vet2\n        self.adj_list\n            .entry(vet1)\n            .and_modify(|v| v.retain(|&e| e != vet2));\n        self.adj_list\n            .entry(vet2)\n            .and_modify(|v| v.retain(|&e| e != vet1));\n    }\n\n    /* Add vertex */\n    pub fn add_vertex(&mut self, vet: Vertex) {\n        if self.adj_list.contains_key(&vet) {\n            return;\n        }\n        // Add a new linked list in the adjacency list\n        self.adj_list.insert(vet, vec![]);\n    }\n\n    /* Remove vertex */\n    #[allow(unused)]\n    pub fn remove_vertex(&mut self, vet: Vertex) {\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        self.adj_list.remove(&vet);\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for list in self.adj_list.values_mut() {\n            list.retain(|&v| v != vet);\n        }\n    }\n\n    /* Print adjacency list */\n    pub fn print(&self) {\n        println!(\"Adjacency list =\");\n        for (vertex, list) in &self.adj_list {\n            let list = list.iter().map(|vertex| vertex.val).collect::<Vec<i32>>();\n            println!(\"{}: {:?},\", vertex.val, list);\n        }\n    }\n}\n
graph_adjacency_list.c
/* Node structure */\ntypedef struct AdjListNode {\n    Vertex *vertex;           // Vertex\n    struct AdjListNode *next; // Successor node\n} AdjListNode;\n\n/* Find node corresponding to vertex */\nAdjListNode *findNode(GraphAdjList *graph, Vertex *vet) {\n    for (int i = 0; i < graph->size; i++) {\n        if (graph->heads[i]->vertex == vet) {\n            return graph->heads[i];\n        }\n    }\n    return NULL;\n}\n\n/* Add edge helper function */\nvoid addEdgeHelper(AdjListNode *head, Vertex *vet) {\n    AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode));\n    node->vertex = vet;\n    // Head insertion\n    node->next = head->next;\n    head->next = node;\n}\n\n/* Remove edge helper function */\nvoid removeEdgeHelper(AdjListNode *head, Vertex *vet) {\n    AdjListNode *pre = head;\n    AdjListNode *cur = head->next;\n    // Search for node corresponding to vet in list\n    while (cur != NULL && cur->vertex != vet) {\n        pre = cur;\n        cur = cur->next;\n    }\n    if (cur == NULL)\n        return;\n    // Remove node corresponding to vet from list\n    pre->next = cur->next;\n    // Free memory\n    free(cur);\n}\n\n/* Undirected graph class based on adjacency list */\ntypedef struct {\n    AdjListNode *heads[MAX_SIZE]; // Node array\n    int size;                     // Node count\n} GraphAdjList;\n\n/* Constructor */\nGraphAdjList *newGraphAdjList() {\n    GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList));\n    if (!graph) {\n        return NULL;\n    }\n    graph->size = 0;\n    for (int i = 0; i < MAX_SIZE; i++) {\n        graph->heads[i] = NULL;\n    }\n    return graph;\n}\n\n/* Destructor */\nvoid delGraphAdjList(GraphAdjList *graph) {\n    for (int i = 0; i < graph->size; i++) {\n        AdjListNode *cur = graph->heads[i];\n        while (cur != NULL) {\n            AdjListNode *next = cur->next;\n            if (cur != graph->heads[i]) {\n                free(cur);\n            }\n            cur = next;\n        }\n        free(graph->heads[i]->vertex);\n        free(graph->heads[i]);\n    }\n    free(graph);\n}\n\n/* Find node corresponding to vertex */\nAdjListNode *findNode(GraphAdjList *graph, Vertex *vet) {\n    for (int i = 0; i < graph->size; i++) {\n        if (graph->heads[i]->vertex == vet) {\n            return graph->heads[i];\n        }\n    }\n    return NULL;\n}\n\n/* Add edge */\nvoid addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {\n    AdjListNode *head1 = findNode(graph, vet1);\n    AdjListNode *head2 = findNode(graph, vet2);\n    assert(head1 != NULL && head2 != NULL && head1 != head2);\n    // Add edge vet1 - vet2\n    addEdgeHelper(head1, vet2);\n    addEdgeHelper(head2, vet1);\n}\n\n/* Remove edge */\nvoid removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {\n    AdjListNode *head1 = findNode(graph, vet1);\n    AdjListNode *head2 = findNode(graph, vet2);\n    assert(head1 != NULL && head2 != NULL);\n    // Remove edge vet1 - vet2\n    removeEdgeHelper(head1, head2->vertex);\n    removeEdgeHelper(head2, head1->vertex);\n}\n\n/* Add vertex */\nvoid addVertex(GraphAdjList *graph, Vertex *vet) {\n    assert(graph != NULL && graph->size < MAX_SIZE);\n    AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode));\n    head->vertex = vet;\n    head->next = NULL;\n    // Add a new linked list in the adjacency list\n    graph->heads[graph->size++] = head;\n}\n\n/* Remove vertex */\nvoid removeVertex(GraphAdjList *graph, Vertex *vet) {\n    AdjListNode *node = findNode(graph, vet);\n    assert(node != NULL);\n    // Remove the linked list corresponding to vertex vet in the adjacency list\n    AdjListNode *cur = node, *pre = NULL;\n    while (cur) {\n        pre = cur;\n        cur = cur->next;\n        free(pre);\n    }\n    // Traverse the linked lists of other vertices and remove all edges containing vet\n    for (int i = 0; i < graph->size; i++) {\n        cur = graph->heads[i];\n        pre = NULL;\n        while (cur) {\n            pre = cur;\n            cur = cur->next;\n            if (cur && cur->vertex == vet) {\n                pre->next = cur->next;\n                free(cur);\n                break;\n            }\n        }\n    }\n    // Move vertices after this vertex forward to fill gap\n    int i;\n    for (i = 0; i < graph->size; i++) {\n        if (graph->heads[i] == node)\n            break;\n    }\n    for (int j = i; j < graph->size - 1; j++) {\n        graph->heads[j] = graph->heads[j + 1];\n    }\n    graph->size--;\n    free(vet);\n}\n
graph_adjacency_list.kt
/* Undirected graph class based on adjacency list */\nclass GraphAdjList(edges: Array<Array<Vertex?>>) {\n    // Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    val adjList = HashMap<Vertex, MutableList<Vertex>>()\n\n    /* Constructor */\n    init {\n        // Add all vertices and edges\n        for (edge in edges) {\n            addVertex(edge[0]!!)\n            addVertex(edge[1]!!)\n            addEdge(edge[0]!!, edge[1]!!)\n        }\n    }\n\n    /* Get the number of vertices */\n    fun size(): Int {\n        return adjList.size\n    }\n\n    /* Add edge */\n    fun addEdge(vet1: Vertex, vet2: Vertex) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw IllegalArgumentException()\n        // Add edge vet1 - vet2\n        adjList[vet1]?.add(vet2)\n        adjList[vet2]?.add(vet1)\n    }\n\n    /* Remove edge */\n    fun removeEdge(vet1: Vertex, vet2: Vertex) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw IllegalArgumentException()\n        // Remove edge vet1 - vet2\n        adjList[vet1]?.remove(vet2)\n        adjList[vet2]?.remove(vet1)\n    }\n\n    /* Add vertex */\n    fun addVertex(vet: Vertex) {\n        if (adjList.containsKey(vet))\n            return\n        // Add a new linked list in the adjacency list\n        adjList[vet] = mutableListOf()\n    }\n\n    /* Remove vertex */\n    fun removeVertex(vet: Vertex) {\n        if (!adjList.containsKey(vet))\n            throw IllegalArgumentException()\n        // Remove the linked list corresponding to vertex vet in the adjacency list\n        adjList.remove(vet)\n        // Traverse the linked lists of other vertices and remove all edges containing vet\n        for (list in adjList.values) {\n            list.remove(vet)\n        }\n    }\n\n    /* Print adjacency list */\n    fun print() {\n        println(\"Adjacency list =\")\n        for (pair in adjList.entries) {\n            val tmp = mutableListOf<Int>()\n            for (vertex in pair.value) {\n                tmp.add(vertex._val)\n            }\n            println(\"${pair.key._val}: $tmp,\")\n        }\n    }\n}\n
graph_adjacency_list.rb
### Undirected graph class based on adjacency list ###\nclass GraphAdjList\n  attr_reader :adj_list\n\n  ### Constructor ###\n  def initialize(edges)\n    # Adjacency list, key: vertex, value: all adjacent vertices of that vertex\n    @adj_list = {}\n    # Add all vertices and edges\n    for edge in edges\n      add_vertex(edge[0])\n      add_vertex(edge[1])\n      add_edge(edge[0], edge[1])\n    end\n  end\n\n  ### Get number of vertices ###\n  def size\n    @adj_list.length\n  end\n\n  ### Add edge ###\n  def add_edge(vet1, vet2)\n    raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)\n\n    @adj_list[vet1] << vet2\n    @adj_list[vet2] << vet1\n  end\n\n  ### Delete edge ###\n  def remove_edge(vet1, vet2)\n    raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)\n\n    # Remove edge vet1 - vet2\n    @adj_list[vet1].delete(vet2)\n    @adj_list[vet2].delete(vet1)\n  end\n\n  ### Add vertex ###\n  def add_vertex(vet)\n    return if @adj_list.include?(vet)\n\n    # Add a new linked list in the adjacency list\n    @adj_list[vet] = []\n  end\n\n  ### Delete vertex ###\n  def remove_vertex(vet)\n    raise ArgumentError unless @adj_list.include?(vet)\n\n    # Remove the linked list corresponding to vertex vet in the adjacency list\n    @adj_list.delete(vet)\n    # Traverse the linked lists of other vertices and remove all edges containing vet\n    for vertex in @adj_list\n      @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet)\n    end\n  end\n\n  ### Print adjacency list ###\n  def __print__\n    puts 'Adjacency list ='\n    for vertex in @adj_list\n      tmp = @adj_list[vertex.first].map { |v| v.val }\n      puts \"#{vertex.first.val}: #{tmp},\"\n    end\n  end\nend\n
","path":["Chapter 9. Graph","9.2   Basic Operations on Graphs"],"tags":[]},{"location":"chapter_graph/graph_operations/#923-efficiency-comparison","level":2,"title":"9.2.3   Efficiency Comparison","text":"

Assuming the graph has \\(n\\) vertices and \\(m\\) edges, Table 9-2 compares the time efficiency and space efficiency of adjacency matrices and adjacency lists. Note that the adjacency list (linked list) corresponds to the implementation in this text, while the adjacency list (hash table) refers specifically to the implementation where all linked lists are replaced with hash tables.

Table 9-2   Comparison of adjacency matrix and adjacency list

Adjacency matrix Adjacency list (linked list) Adjacency list (hash table) Determine adjacency \\(O(1)\\) \\(O(n)\\) \\(O(1)\\) Add an edge \\(O(1)\\) \\(O(1)\\) \\(O(1)\\) Remove an edge \\(O(1)\\) \\(O(n)\\) \\(O(1)\\) Add a vertex \\(O(n)\\) \\(O(1)\\) \\(O(1)\\) Remove a vertex \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n)\\) Memory space usage \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n + m)\\)

Observing Table 9-2, it appears that the adjacency list (hash table) has the best time efficiency and space efficiency. However, in practice, operating on edges in the adjacency matrix is more efficient, requiring only a single array access or assignment operation. Overall, adjacency matrices embody the principle of \"trading space for time\", while adjacency lists embody \"trading time for space\".

","path":["Chapter 9. Graph","9.2   Basic Operations on Graphs"],"tags":[]},{"location":"chapter_graph/graph_traversal/","level":1,"title":"9.3   Graph Traversal","text":"

Trees represent \"one-to-many\" relationships, while graphs have a higher degree of freedom and can represent any \"many-to-many\" relationships. Therefore, we can view trees as a special case of graphs. Clearly, tree traversal operations are also a special case of graph traversal operations.

Both graphs and trees require the application of search algorithms to implement traversal operations. Graph traversal methods can also be divided into two types: breadth-first traversal and depth-first traversal.

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#931-breadth-first-search","level":2,"title":"9.3.1   Breadth-First Search","text":"

Breadth-first search is a near-to-far traversal method that, starting from a certain node, always prioritizes visiting the nearest vertices and expands outward layer by layer. As shown in Figure 9-9, starting from the top-left vertex, first traverse all adjacent vertices of that vertex, then traverse all adjacent vertices of the next vertex, and so on, until all vertices have been visited.

Figure 9-9   Breadth-first search of a graph

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#1-algorithm-implementation","level":3,"title":"1.   Algorithm Implementation","text":"

BFS is typically implemented with the help of a queue, as shown in the code below. The queue has a \"first in, first out\" property, which aligns with the BFS idea of \"near to far\".

  1. Add the starting vertex startVet to the queue and begin the loop.
  2. In each iteration of the loop, pop the vertex at the front of the queue and record it as visited, then add all adjacent vertices of that vertex to the back of the queue.
  3. Repeat step 2. until all vertices have been visited.

To prevent revisiting vertices, we use a hash set visited to record which nodes have been visited.

Tip

A hash set can be viewed as a hash table that stores only key without storing value. It can perform addition, deletion, lookup, and modification operations on key in \\(O(1)\\) time complexity. Based on the uniqueness of key, hash sets are typically used for data deduplication and similar scenarios.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_bfs.py
def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:\n    \"\"\"Breadth-first traversal\"\"\"\n    # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\n    # Vertex traversal sequence\n    res = []\n    # Hash set for recording vertices that have been visited\n    visited = set[Vertex]([start_vet])\n    # Queue used to implement BFS\n    que = deque[Vertex]([start_vet])\n    # Starting from vertex vet, loop until all vertices are visited\n    while len(que) > 0:\n        vet = que.popleft()  # Dequeue the front vertex\n        res.append(vet)  # Record visited vertex\n        # Traverse all adjacent vertices of this vertex\n        for adj_vet in graph.adj_list[vet]:\n            if adj_vet in visited:\n                continue  # Skip vertices that have been visited\n            que.append(adj_vet)  # Only enqueue unvisited vertices\n            visited.add(adj_vet)  # Mark this vertex as visited\n    # Return vertex traversal sequence\n    return res\n
graph_bfs.cpp
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nvector<Vertex *> graphBFS(GraphAdjList &graph, Vertex *startVet) {\n    // Vertex traversal sequence\n    vector<Vertex *> res;\n    // Hash set for recording vertices that have been visited\n    unordered_set<Vertex *> visited = {startVet};\n    // Queue used to implement BFS\n    queue<Vertex *> que;\n    que.push(startVet);\n    // Starting from vertex vet, loop until all vertices are visited\n    while (!que.empty()) {\n        Vertex *vet = que.front();\n        que.pop();          // Dequeue the front vertex\n        res.push_back(vet); // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for (auto adjVet : graph.adjList[vet]) {\n            if (visited.count(adjVet))\n                continue;            // Skip vertices that have been visited\n            que.push(adjVet);        // Only enqueue unvisited vertices\n            visited.emplace(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res;\n}\n
graph_bfs.java
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nList<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = new ArrayList<>();\n    // Hash set for recording vertices that have been visited\n    Set<Vertex> visited = new HashSet<>();\n    visited.add(startVet);\n    // Queue used to implement BFS\n    Queue<Vertex> que = new LinkedList<>();\n    que.offer(startVet);\n    // Starting from vertex vet, loop until all vertices are visited\n    while (!que.isEmpty()) {\n        Vertex vet = que.poll(); // Dequeue the front vertex\n        res.add(vet);            // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for (Vertex adjVet : graph.adjList.get(vet)) {\n            if (visited.contains(adjVet))\n                continue;        // Skip vertices that have been visited\n            que.offer(adjVet);   // Only enqueue unvisited vertices\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res;\n}\n
graph_bfs.cs
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nList<Vertex> GraphBFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = [];\n    // Hash set for recording vertices that have been visited\n    HashSet<Vertex> visited = [startVet];\n    // Queue used to implement BFS\n    Queue<Vertex> que = new();\n    que.Enqueue(startVet);\n    // Starting from vertex vet, loop until all vertices are visited\n    while (que.Count > 0) {\n        Vertex vet = que.Dequeue(); // Dequeue the front vertex\n        res.Add(vet);               // Record visited vertex\n        foreach (Vertex adjVet in graph.adjList[vet]) {\n            if (visited.Contains(adjVet)) {\n                continue;          // Skip vertices that have been visited\n            }\n            que.Enqueue(adjVet);   // Only enqueue unvisited vertices\n            visited.Add(adjVet);   // Mark this vertex as visited\n        }\n    }\n\n    // Return vertex traversal sequence\n    return res;\n}\n
graph_bfs.go
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunc graphBFS(g *graphAdjList, startVet Vertex) []Vertex {\n    // Vertex traversal sequence\n    res := make([]Vertex, 0)\n    // Hash set for recording vertices that have been visited\n    visited := make(map[Vertex]struct{})\n    visited[startVet] = struct{}{}\n    // Queue used to implement BFS, using slice to simulate queue\n    queue := make([]Vertex, 0)\n    queue = append(queue, startVet)\n    // Starting from vertex vet, loop until all vertices are visited\n    for len(queue) > 0 {\n        // Dequeue the front vertex\n        vet := queue[0]\n        queue = queue[1:]\n        // Record visited vertex\n        res = append(res, vet)\n        // Traverse all adjacent vertices of this vertex\n        for _, adjVet := range g.adjList[vet] {\n            _, isExist := visited[adjVet]\n            // Only enqueue unvisited vertices\n            if !isExist {\n                queue = append(queue, adjVet)\n                visited[adjVet] = struct{}{}\n            }\n        }\n    }\n    // Return vertex traversal sequence\n    return res\n}\n
graph_bfs.swift
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunc graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {\n    // Vertex traversal sequence\n    var res: [Vertex] = []\n    // Hash set for recording vertices that have been visited\n    var visited: Set<Vertex> = [startVet]\n    // Queue used to implement BFS\n    var que: [Vertex] = [startVet]\n    // Starting from vertex vet, loop until all vertices are visited\n    while !que.isEmpty {\n        let vet = que.removeFirst() // Dequeue the front vertex\n        res.append(vet) // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for adjVet in graph.adjList[vet] ?? [] {\n            if visited.contains(adjVet) {\n                continue // Skip vertices that have been visited\n            }\n            que.append(adjVet) // Only enqueue unvisited vertices\n            visited.insert(adjVet) // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res\n}\n
graph_bfs.js
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunction graphBFS(graph, startVet) {\n    // Vertex traversal sequence\n    const res = [];\n    // Hash set for recording vertices that have been visited\n    const visited = new Set();\n    visited.add(startVet);\n    // Queue used to implement BFS\n    const que = [startVet];\n    // Starting from vertex vet, loop until all vertices are visited\n    while (que.length) {\n        const vet = que.shift(); // Dequeue the front vertex\n        res.push(vet); // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for (const adjVet of graph.adjList.get(vet) ?? []) {\n            if (visited.has(adjVet)) {\n                continue; // Skip vertices that have been visited\n            }\n            que.push(adjVet); // Only enqueue unvisited vertices\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res;\n}\n
graph_bfs.ts
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunction graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {\n    // Vertex traversal sequence\n    const res: Vertex[] = [];\n    // Hash set for recording vertices that have been visited\n    const visited: Set<Vertex> = new Set();\n    visited.add(startVet);\n    // Queue used to implement BFS\n    const que = [startVet];\n    // Starting from vertex vet, loop until all vertices are visited\n    while (que.length) {\n        const vet = que.shift(); // Dequeue the front vertex\n        res.push(vet); // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for (const adjVet of graph.adjList.get(vet) ?? []) {\n            if (visited.has(adjVet)) {\n                continue; // Skip vertices that have been visited\n            }\n            que.push(adjVet); // Only enqueue unvisited\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res;\n}\n
graph_bfs.dart
/* Breadth-first traversal */\nList<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {\n  // Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\n  // Vertex traversal sequence\n  List<Vertex> res = [];\n  // Hash set for recording vertices that have been visited\n  Set<Vertex> visited = {};\n  visited.add(startVet);\n  // Queue used to implement BFS\n  Queue<Vertex> que = Queue();\n  que.add(startVet);\n  // Starting from vertex vet, loop until all vertices are visited\n  while (que.isNotEmpty) {\n    Vertex vet = que.removeFirst(); // Dequeue the front vertex\n    res.add(vet); // Record visited vertex\n    // Traverse all adjacent vertices of this vertex\n    for (Vertex adjVet in graph.adjList[vet]!) {\n      if (visited.contains(adjVet)) {\n        continue; // Skip vertices that have been visited\n      }\n      que.add(adjVet); // Only enqueue unvisited vertices\n      visited.add(adjVet); // Mark this vertex as visited\n    }\n  }\n  // Return vertex traversal sequence\n  return res;\n}\n
graph_bfs.rs
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {\n    // Vertex traversal sequence\n    let mut res = vec![];\n    // Hash set for recording vertices that have been visited\n    let mut visited = HashSet::new();\n    visited.insert(start_vet);\n    // Queue used to implement BFS\n    let mut que = VecDeque::new();\n    que.push_back(start_vet);\n    // Starting from vertex vet, loop until all vertices are visited\n    while let Some(vet) = que.pop_front() {\n        res.push(vet); // Record visited vertex\n\n        // Traverse all adjacent vertices of this vertex\n        if let Some(adj_vets) = graph.adj_list.get(&vet) {\n            for &adj_vet in adj_vets {\n                if visited.contains(&adj_vet) {\n                    continue; // Skip vertices that have been visited\n                }\n                que.push_back(adj_vet); // Only enqueue unvisited vertices\n                visited.insert(adj_vet); // Mark this vertex as visited\n            }\n        }\n    }\n    // Return vertex traversal sequence\n    res\n}\n
graph_bfs.c
/* Node queue structure */\ntypedef struct {\n    Vertex *vertices[MAX_SIZE];\n    int front, rear, size;\n} Queue;\n\n/* Constructor */\nQueue *newQueue() {\n    Queue *q = (Queue *)malloc(sizeof(Queue));\n    q->front = q->rear = q->size = 0;\n    return q;\n}\n\n/* Check if the queue is empty */\nint isEmpty(Queue *q) {\n    return q->size == 0;\n}\n\n/* Enqueue operation */\nvoid enqueue(Queue *q, Vertex *vet) {\n    q->vertices[q->rear] = vet;\n    q->rear = (q->rear + 1) % MAX_SIZE;\n    q->size++;\n}\n\n/* Dequeue operation */\nVertex *dequeue(Queue *q) {\n    Vertex *vet = q->vertices[q->front];\n    q->front = (q->front + 1) % MAX_SIZE;\n    q->size--;\n    return vet;\n}\n\n/* Check if vertex has been visited */\nint isVisited(Vertex **visited, int size, Vertex *vet) {\n    // Traverse to find node using O(n) time\n    for (int i = 0; i < size; i++) {\n        if (visited[i] == vet)\n            return 1;\n    }\n    return 0;\n}\n\n/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nvoid graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) {\n    // Queue used to implement BFS\n    Queue *queue = newQueue();\n    enqueue(queue, startVet);\n    visited[(*visitedSize)++] = startVet;\n    // Starting from vertex vet, loop until all vertices are visited\n    while (!isEmpty(queue)) {\n        Vertex *vet = dequeue(queue); // Dequeue the front vertex\n        res[(*resSize)++] = vet;      // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        AdjListNode *node = findNode(graph, vet);\n        while (node != NULL) {\n            // Skip vertices that have been visited\n            if (!isVisited(visited, *visitedSize, node->vertex)) {\n                enqueue(queue, node->vertex);             // Only enqueue unvisited vertices\n                visited[(*visitedSize)++] = node->vertex; // Mark this vertex as visited\n            }\n            node = node->next;\n        }\n    }\n    // Free memory\n    free(queue);\n}\n
graph_bfs.kt
/* Breadth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList<Vertex?> {\n    // Vertex traversal sequence\n    val res = mutableListOf<Vertex?>()\n    // Hash set for recording vertices that have been visited\n    val visited = HashSet<Vertex>()\n    visited.add(startVet)\n    // Queue used to implement BFS\n    val que = LinkedList<Vertex>()\n    que.offer(startVet)\n    // Starting from vertex vet, loop until all vertices are visited\n    while (!que.isEmpty()) {\n        val vet = que.poll() // Dequeue the front vertex\n        res.add(vet)         // Record visited vertex\n        // Traverse all adjacent vertices of this vertex\n        for (adjVet in graph.adjList[vet]!!) {\n            if (visited.contains(adjVet))\n                continue        // Skip vertices that have been visited\n            que.offer(adjVet)   // Only enqueue unvisited vertices\n            visited.add(adjVet) // Mark this vertex as visited\n        }\n    }\n    // Return vertex traversal sequence\n    return res\n}\n
graph_bfs.rb
### Breadth-first traversal ###\ndef graph_bfs(graph, start_vet)\n  # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\n  # Vertex traversal sequence\n  res = []\n  # Hash set for recording vertices that have been visited\n  visited = Set.new([start_vet])\n  # Queue used to implement BFS\n  que = [start_vet]\n  # Starting from vertex vet, loop until all vertices are visited\n  while que.length > 0\n    vet = que.shift # Dequeue the front vertex\n    res << vet # Record visited vertex\n    # Traverse all adjacent vertices of this vertex\n    for adj_vet in graph.adj_list[vet]\n      next if visited.include?(adj_vet) # Skip vertices that have been visited\n      que << adj_vet # Only enqueue unvisited vertices\n      visited.add(adj_vet) # Mark this vertex as visited\n    end\n  end\n  # Return vertex traversal sequence\n  res\nend\n

The code is relatively abstract; it is recommended to refer to Figure 9-10 to deepen understanding.

<1><2><3><4><5><6><7><8><9><10><11>

Figure 9-10   Steps of breadth-first search of a graph

Is the breadth-first traversal sequence unique?

Not unique. Breadth-first search only requires traversing in a \"near to far\" order, and the traversal order of vertices at the same distance can be arbitrarily shuffled. Taking Figure 9-10 as an example, the visit order of vertices \\(1\\) and \\(3\\) can be swapped, as can the visit order of vertices \\(2\\), \\(4\\), and \\(6\\).

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#2-complexity-analysis","level":3,"title":"2.   Complexity Analysis","text":"

Time complexity: All vertices will be enqueued and dequeued once, using \\(O(|V|)\\) time; in the process of traversing adjacent vertices, since it is an undirected graph, all edges will be visited \\(2\\) times, using \\(O(2|E|)\\) time; overall using \\(O(|V| + |E|)\\) time.

Space complexity: The list res, hash set visited, and queue que can contain at most \\(|V|\\) vertices, using \\(O(|V|)\\) space.

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#932-depth-first-search","level":2,"title":"9.3.2   Depth-First Search","text":"

Depth-first search is a traversal method that prioritizes going as far as possible, then backtracks when no path remains. As shown in Figure 9-11, starting from the top-left vertex, visit an adjacent vertex of the current vertex, continuing until reaching a dead end, then return and continue going as far as possible before returning again, and so on, until all vertices have been traversed.

Figure 9-11   Depth-first search of a graph

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#1-algorithm-implementation_1","level":3,"title":"1.   Algorithm Implementation","text":"

This \"go as far as possible then return\" algorithm paradigm is typically implemented using recursion. Similar to breadth-first search, in depth-first search we also need a hash set visited to record visited vertices and avoid revisiting.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_dfs.py
def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex):\n    \"\"\"Depth-first traversal helper function\"\"\"\n    res.append(vet)  # Record visited vertex\n    visited.add(vet)  # Mark this vertex as visited\n    # Traverse all adjacent vertices of this vertex\n    for adjVet in graph.adj_list[vet]:\n        if adjVet in visited:\n            continue  # Skip vertices that have been visited\n        # Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet)\n\ndef graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:\n    \"\"\"Depth-first traversal\"\"\"\n    # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\n    # Vertex traversal sequence\n    res = []\n    # Hash set for recording vertices that have been visited\n    visited = set[Vertex]()\n    dfs(graph, visited, res, start_vet)\n    return res\n
graph_dfs.cpp
/* Depth-first traversal helper function */\nvoid dfs(GraphAdjList &graph, unordered_set<Vertex *> &visited, vector<Vertex *> &res, Vertex *vet) {\n    res.push_back(vet);   // Record visited vertex\n    visited.emplace(vet); // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for (Vertex *adjVet : graph.adjList[vet]) {\n        if (visited.count(adjVet))\n            continue; // Skip vertices that have been visited\n        // Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nvector<Vertex *> graphDFS(GraphAdjList &graph, Vertex *startVet) {\n    // Vertex traversal sequence\n    vector<Vertex *> res;\n    // Hash set for recording vertices that have been visited\n    unordered_set<Vertex *> visited;\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
graph_dfs.java
/* Depth-first traversal helper function */\nvoid dfs(GraphAdjList graph, Set<Vertex> visited, List<Vertex> res, Vertex vet) {\n    res.add(vet);     // Record visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for (Vertex adjVet : graph.adjList.get(vet)) {\n        if (visited.contains(adjVet))\n            continue; // Skip vertices that have been visited\n        // Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nList<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = new ArrayList<>();\n    // Hash set for recording vertices that have been visited\n    Set<Vertex> visited = new HashSet<>();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
graph_dfs.cs
/* Depth-first traversal helper function */\nvoid DFS(GraphAdjList graph, HashSet<Vertex> visited, List<Vertex> res, Vertex vet) {\n    res.Add(vet);     // Record visited vertex\n    visited.Add(vet); // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    foreach (Vertex adjVet in graph.adjList[vet]) {\n        if (visited.Contains(adjVet)) {\n            continue; // Skip vertices that have been visited\n        }\n        // Recursively visit adjacent vertices\n        DFS(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nList<Vertex> GraphDFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = [];\n    // Hash set for recording vertices that have been visited\n    HashSet<Vertex> visited = [];\n    DFS(graph, visited, res, startVet);\n    return res;\n}\n
graph_dfs.go
/* Depth-first traversal helper function */\nfunc dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) {\n    // append operation returns a new reference, must reassign original reference to new slice's reference\n    *res = append(*res, vet)\n    visited[vet] = struct{}{}\n    // Traverse all adjacent vertices of this vertex\n    for _, adjVet := range g.adjList[vet] {\n        _, isExist := visited[adjVet]\n        // Recursively visit adjacent vertices\n        if !isExist {\n            dfs(g, visited, res, adjVet)\n        }\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunc graphDFS(g *graphAdjList, startVet Vertex) []Vertex {\n    // Vertex traversal sequence\n    res := make([]Vertex, 0)\n    // Hash set for recording vertices that have been visited\n    visited := make(map[Vertex]struct{})\n    dfs(g, visited, &res, startVet)\n    // Return vertex traversal sequence\n    return res\n}\n
graph_dfs.swift
/* Depth-first traversal helper function */\nfunc dfs(graph: GraphAdjList, visited: inout Set<Vertex>, res: inout [Vertex], vet: Vertex) {\n    res.append(vet) // Record visited vertex\n    visited.insert(vet) // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for adjVet in graph.adjList[vet] ?? [] {\n        if visited.contains(adjVet) {\n            continue // Skip vertices that have been visited\n        }\n        // Recursively visit adjacent vertices\n        dfs(graph: graph, visited: &visited, res: &res, vet: adjVet)\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunc graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {\n    // Vertex traversal sequence\n    var res: [Vertex] = []\n    // Hash set for recording vertices that have been visited\n    var visited: Set<Vertex> = []\n    dfs(graph: graph, visited: &visited, res: &res, vet: startVet)\n    return res\n}\n
graph_dfs.js
/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunction dfs(graph, visited, res, vet) {\n    res.push(vet); // Record visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for (const adjVet of graph.adjList.get(vet)) {\n        if (visited.has(adjVet)) {\n            continue; // Skip vertices that have been visited\n        }\n        // Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunction graphDFS(graph, startVet) {\n    // Vertex traversal sequence\n    const res = [];\n    // Hash set for recording vertices that have been visited\n    const visited = new Set();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
graph_dfs.ts
/* Depth-first traversal helper function */\nfunction dfs(\n    graph: GraphAdjList,\n    visited: Set<Vertex>,\n    res: Vertex[],\n    vet: Vertex\n): void {\n    res.push(vet); // Record visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for (const adjVet of graph.adjList.get(vet)) {\n        if (visited.has(adjVet)) {\n            continue; // Skip vertices that have been visited\n        }\n        // Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfunction graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {\n    // Vertex traversal sequence\n    const res: Vertex[] = [];\n    // Hash set for recording vertices that have been visited\n    const visited: Set<Vertex> = new Set();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
graph_dfs.dart
/* Depth-first traversal helper function */\nvoid dfs(\n  GraphAdjList graph,\n  Set<Vertex> visited,\n  List<Vertex> res,\n  Vertex vet,\n) {\n  res.add(vet); // Record visited vertex\n  visited.add(vet); // Mark this vertex as visited\n  // Traverse all adjacent vertices of this vertex\n  for (Vertex adjVet in graph.adjList[vet]!) {\n    if (visited.contains(adjVet)) {\n      continue; // Skip vertices that have been visited\n    }\n    // Recursively visit adjacent vertices\n    dfs(graph, visited, res, adjVet);\n  }\n}\n\n/* Depth-first traversal */\nList<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {\n  // Vertex traversal sequence\n  List<Vertex> res = [];\n  // Hash set for recording vertices that have been visited\n  Set<Vertex> visited = {};\n  dfs(graph, visited, res, startVet);\n  return res;\n}\n
graph_dfs.rs
/* Depth-first traversal helper function */\nfn dfs(graph: &GraphAdjList, visited: &mut HashSet<Vertex>, res: &mut Vec<Vertex>, vet: Vertex) {\n    res.push(vet); // Record visited vertex\n    visited.insert(vet); // Mark this vertex as visited\n                         // Traverse all adjacent vertices of this vertex\n    if let Some(adj_vets) = graph.adj_list.get(&vet) {\n        for &adj_vet in adj_vets {\n            if visited.contains(&adj_vet) {\n                continue; // Skip vertices that have been visited\n            }\n            // Recursively visit adjacent vertices\n            dfs(graph, visited, res, adj_vet);\n        }\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {\n    // Vertex traversal sequence\n    let mut res = vec![];\n    // Hash set for recording vertices that have been visited\n    let mut visited = HashSet::new();\n    dfs(&graph, &mut visited, &mut res, start_vet);\n\n    res\n}\n
graph_dfs.c
/* Check if vertex has been visited */\nint isVisited(Vertex **res, int size, Vertex *vet) {\n    // Traverse to find node using O(n) time\n    for (int i = 0; i < size; i++) {\n        if (res[i] == vet) {\n            return 1;\n        }\n    }\n    return 0;\n}\n\n/* Depth-first traversal helper function */\nvoid dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) {\n    // Record visited vertex\n    res[(*resSize)++] = vet;\n    // Traverse all adjacent vertices of this vertex\n    AdjListNode *node = findNode(graph, vet);\n    while (node != NULL) {\n        // Skip vertices that have been visited\n        if (!isVisited(res, *resSize, node->vertex)) {\n            // Recursively visit adjacent vertices\n            dfs(graph, res, resSize, node->vertex);\n        }\n        node = node->next;\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nvoid graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) {\n    dfs(graph, res, resSize, startVet);\n}\n
graph_dfs.kt
/* Depth-first traversal helper function */\nfun dfs(\n    graph: GraphAdjList,\n    visited: MutableSet<Vertex?>,\n    res: MutableList<Vertex?>,\n    vet: Vertex?\n) {\n    res.add(vet)     // Record visited vertex\n    visited.add(vet) // Mark this vertex as visited\n    // Traverse all adjacent vertices of this vertex\n    for (adjVet in graph.adjList[vet]!!) {\n        if (visited.contains(adjVet))\n            continue  // Skip vertices that have been visited\n        // Recursively visit adjacent vertices\n        dfs(graph, visited, res, adjVet)\n    }\n}\n\n/* Depth-first traversal */\n// Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\nfun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList<Vertex?> {\n    // Vertex traversal sequence\n    val res = mutableListOf<Vertex?>()\n    // Hash set for recording vertices that have been visited\n    val visited = HashSet<Vertex?>()\n    dfs(graph, visited, res, startVet)\n    return res\n}\n
graph_dfs.rb
### Depth-first traversal helper function ###\ndef dfs(graph, visited, res, vet)\n  res << vet # Record visited vertex\n  visited.add(vet) # Mark this vertex as visited\n  # Traverse all adjacent vertices of this vertex\n  for adj_vet in graph.adj_list[vet]\n    next if visited.include?(adj_vet) # Skip vertices that have been visited\n    # Recursively visit adjacent vertices\n    dfs(graph, visited, res, adj_vet)\n  end\nend\n\n### Depth-first traversal ###\ndef graph_dfs(graph, start_vet)\n  # Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex\n  # Vertex traversal sequence\n  res = []\n  # Hash set for recording vertices that have been visited\n  visited = Set.new\n  dfs(graph, visited, res, start_vet)\n  res\nend\n

The algorithm flow of depth-first search is shown in Figure 9-12.

  • Straight dashed lines represent downward recursion, indicating that a new recursive method has been initiated to visit a new vertex.
  • Curved dashed lines represent upward backtracking, indicating that this recursive method has returned to the position where it was initiated.

To deepen understanding, it is recommended to combine Figure 9-12 with the code to mentally simulate (or draw out) the entire DFS process, including when each recursive method is initiated and when it returns.

<1><2><3><4><5><6><7><8><9><10><11>

Figure 9-12   Steps of depth-first search of a graph

Is the depth-first traversal sequence unique?

Similar to breadth-first search, the order of depth-first traversal sequences is also not unique. Given a certain vertex, exploring in any direction first is valid, meaning the order of adjacent vertices can be arbitrarily shuffled, all being depth-first search.

Taking tree traversal as an example, \"root \\(\\rightarrow\\) left \\(\\rightarrow\\) right\", \"left \\(\\rightarrow\\) root \\(\\rightarrow\\) right\", and \"left \\(\\rightarrow\\) right \\(\\rightarrow\\) root\" correspond to pre-order, in-order, and post-order traversals, respectively. They represent three different traversal priorities, yet all three belong to depth-first search.

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#2-complexity-analysis_1","level":3,"title":"2.   Complexity Analysis","text":"

Time complexity: All vertices will be visited \\(1\\) time, using \\(O(|V|)\\) time; all edges will be visited \\(2\\) times, using \\(O(2|E|)\\) time; overall using \\(O(|V| + |E|)\\) time.

Space complexity: The list res and hash set visited can contain at most \\(|V|\\) vertices, and the maximum recursion depth is \\(|V|\\), therefore using \\(O(|V|)\\) space.

","path":["Chapter 9. Graph","9.3   Graph Traversal"],"tags":[]},{"location":"chapter_graph/summary/","level":1,"title":"9.4   Summary","text":"","path":["Chapter 9. Graph","9.4   Summary"],"tags":[]},{"location":"chapter_graph/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Graphs consist of vertices and edges and can be represented as a set of vertices and a set of edges.
  • Compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) have a higher degree of freedom and are therefore more complex.
  • Directed graphs have edges with directionality, connected graphs have all vertices reachable from any vertex, and weighted graphs have edges that each contain a weight variable.
  • Adjacency matrices use matrices to represent graphs, where each row (column) represents a vertex, and matrix elements represent edges, using \\(1\\) or \\(0\\) to indicate whether two vertices have an edge or not. Adjacency matrices are highly efficient for addition, deletion, lookup, and modification operations, but consume significant space.
  • Adjacency lists use multiple linked lists to represent graphs, where the \\(i\\)-th linked list corresponds to vertex \\(i\\) and stores all adjacent vertices of that vertex. Adjacency lists are more space-efficient than adjacency matrices, but have lower time efficiency because they require traversing linked lists to find edges.
  • When linked lists in adjacency lists become too long, they can be converted to red-black trees or hash tables, thereby improving lookup efficiency.
  • From an algorithmic perspective, adjacency matrices embody \"trading space for time\", while adjacency lists embody \"trading time for space\".
  • Graphs can be used to model various real-world systems, such as social networks and subway lines.
  • Trees are a special case of graphs, and tree traversal is a special case of graph traversal.
  • Breadth-first search of graphs is a near-to-far, layer-by-layer expansion search method, typically implemented using a queue.
  • Depth-first search of graphs is a search method that prioritizes going as far as possible and backtracks when no path remains, commonly implemented using recursion.
","path":["Chapter 9. Graph","9.4   Summary"],"tags":[]},{"location":"chapter_graph/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Is a path defined as a sequence of vertices or a sequence of edges?

The definitions in different language versions of Wikipedia are inconsistent: the English version states \"a path is a sequence of edges\", while the Chinese version states \"a path is a sequence of vertices\". The following is the original English text: In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices.

In this text, a path is viewed as a sequence of edges, not a sequence of vertices. This is because there may be multiple edges connecting two vertices, in which case each edge corresponds to a path.

Q: In a disconnected graph, will there be unreachable vertices?

In a disconnected graph, starting from a certain vertex, at least one vertex cannot be reached. Traversing a disconnected graph requires setting multiple starting points to traverse all connected components of the graph.

Q: In an adjacency list, is there a requirement for the order of \"all vertices connected to that vertex\"?

It can be in any order. However, in practical applications, it may be necessary to sort according to specified rules, such as the order in which vertices were added, or the order of vertex values, which helps quickly find vertices \"with certain extreme values\".

","path":["Chapter 9. Graph","9.4   Summary"],"tags":[]},{"location":"chapter_greedy/","level":1,"title":"Chapter 15.   Greedy","text":"

Abstract

Sunflowers turn toward the sun, constantly pursuing the maximum potential for their own growth.

Through rounds of simple choices, greedy strategies gradually lead to the best answer.

","path":["Chapter 15. Greedy","Chapter 15.   Greedy"],"tags":[]},{"location":"chapter_greedy/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 15.1   Greedy Algorithm
  • 15.2   Fractional Knapsack Problem
  • 15.3   Maximum Capacity Problem
  • 15.4   Maximum Product Cutting Problem
  • 15.5   Summary
","path":["Chapter 15. Greedy","Chapter 15.   Greedy"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/","level":1,"title":"15.2   Fractional Knapsack Problem","text":"

Question

Given \\(n\\) items, where the weight of the \\(i\\)-th item is \\(wgt[i-1]\\) and its value is \\(val[i-1]\\), and a knapsack with capacity \\(cap\\). Each item can be selected only once, but a portion of an item can be selected, with the value calculated based on the proportion of weight selected, what is the maximum value of items in the knapsack under the limited capacity? An example is shown in Figure 15-3.

Figure 15-3   Example data for the fractional knapsack problem

The fractional knapsack problem is very similar overall to the 0-1 knapsack problem, with states including the current item \\(i\\) and capacity \\(c\\), and the goal being to maximize value under the limited knapsack capacity.

The difference is that this problem allows selecting only a portion of an item. As shown in Figure 15-4, we can arbitrarily split items and calculate the corresponding value based on the weight proportion.

  1. For item \\(i\\), its value per unit weight is \\(val[i-1] / wgt[i-1]\\), referred to as unit value.
  2. Suppose we put a portion of item \\(i\\) with weight \\(w\\) into the knapsack, then the value added to the knapsack is \\(w \\times val[i-1] / wgt[i-1]\\).

Figure 15-4   Value of items per unit weight

","path":["Chapter 15. Greedy","15.2   Fractional Knapsack Problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#1-greedy-strategy-determination","level":3,"title":"1.   Greedy Strategy Determination","text":"

Maximizing the total value of items in the knapsack is essentially maximizing the value per unit weight of items. From this, we can derive the greedy strategy shown in Figure 15-5.

  1. Sort items by unit value from high to low.
  2. Iterate through all items, greedily selecting the item with the highest unit value in each round.
  3. If the remaining knapsack capacity is insufficient, use a portion of the current item to fill the knapsack.

Figure 15-5   Greedy strategy for the fractional knapsack problem

","path":["Chapter 15. Greedy","15.2   Fractional Knapsack Problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"

We created an Item class to facilitate sorting items by unit value. We loop to make greedy selections, breaking when the knapsack is full and returning the solution:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby fractional_knapsack.py
class Item:\n    \"\"\"Item\"\"\"\n\n    def __init__(self, w: int, v: int):\n        self.w = w  # Item weight\n        self.v = v  # Item value\n\ndef fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Fractional knapsack: Greedy algorithm\"\"\"\n    # Create item list with two attributes: weight, value\n    items = [Item(w, v) for w, v in zip(wgt, val)]\n    # Sort by unit value item.v / item.w from high to low\n    items.sort(key=lambda item: item.v / item.w, reverse=True)\n    # Loop for greedy selection\n    res = 0\n    for item in items:\n        if item.w <= cap:\n            # If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v\n            cap -= item.w\n        else:\n            # If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (item.v / item.w) * cap\n            # No remaining capacity, so break out of the loop\n            break\n    return res\n
fractional_knapsack.cpp
/* Item */\nclass Item {\n  public:\n    int w; // Item weight\n    int v; // Item value\n\n    Item(int w, int v) : w(w), v(v) {\n    }\n};\n\n/* Fractional knapsack: Greedy algorithm */\ndouble fractionalKnapsack(vector<int> &wgt, vector<int> &val, int cap) {\n    // Create item list with two attributes: weight, value\n    vector<Item> items;\n    for (int i = 0; i < wgt.size(); i++) {\n        items.push_back(Item(wgt[i], val[i]));\n    }\n    // Sort by unit value item.v / item.w from high to low\n    sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; });\n    // Loop for greedy selection\n    double res = 0;\n    for (auto &item : items) {\n        if (item.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (double)item.v / item.w * cap;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    return res;\n}\n
fractional_knapsack.java
/* Item */\nclass Item {\n    int w; // Item weight\n    int v; // Item value\n\n    public Item(int w, int v) {\n        this.w = w;\n        this.v = v;\n    }\n}\n\n/* Fractional knapsack: Greedy algorithm */\ndouble fractionalKnapsack(int[] wgt, int[] val, int cap) {\n    // Create item list with two attributes: weight, value\n    Item[] items = new Item[wgt.length];\n    for (int i = 0; i < wgt.length; i++) {\n        items[i] = new Item(wgt[i], val[i]);\n    }\n    // Sort by unit value item.v / item.w from high to low\n    Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w)));\n    // Loop for greedy selection\n    double res = 0;\n    for (Item item : items) {\n        if (item.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (double) item.v / item.w * cap;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    return res;\n}\n
fractional_knapsack.cs
/* Item */\nclass Item(int w, int v) {\n    public int w = w; // Item weight\n    public int v = v; // Item value\n}\n\n/* Fractional knapsack: Greedy algorithm */\ndouble FractionalKnapsack(int[] wgt, int[] val, int cap) {\n    // Create item list with two attributes: weight, value\n    Item[] items = new Item[wgt.Length];\n    for (int i = 0; i < wgt.Length; i++) {\n        items[i] = new Item(wgt[i], val[i]);\n    }\n    // Sort by unit value item.v / item.w from high to low\n    Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w));\n    // Loop for greedy selection\n    double res = 0;\n    foreach (Item item in items) {\n        if (item.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (double)item.v / item.w * cap;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    return res;\n}\n
fractional_knapsack.go
/* Item */\ntype Item struct {\n    w int // Item weight\n    v int // Item value\n}\n\n/* Fractional knapsack: Greedy algorithm */\nfunc fractionalKnapsack(wgt []int, val []int, cap int) float64 {\n    // Create item list with two attributes: weight, value\n    items := make([]Item, len(wgt))\n    for i := 0; i < len(wgt); i++ {\n        items[i] = Item{wgt[i], val[i]}\n    }\n    // Sort by unit value item.v / item.w from high to low\n    sort.Slice(items, func(i, j int) bool {\n        return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w)\n    })\n    // Loop for greedy selection\n    res := 0.0\n    for _, item := range items {\n        if item.w <= cap {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += float64(item.v)\n            cap -= item.w\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += float64(item.v) / float64(item.w) * float64(cap)\n            // No remaining capacity, so break out of the loop\n            break\n        }\n    }\n    return res\n}\n
fractional_knapsack.swift
/* Item */\nclass Item {\n    var w: Int // Item weight\n    var v: Int // Item value\n\n    init(w: Int, v: Int) {\n        self.w = w\n        self.v = v\n    }\n}\n\n/* Fractional knapsack: Greedy algorithm */\nfunc fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double {\n    // Create item list with two attributes: weight, value\n    var items = zip(wgt, val).map { Item(w: $0, v: $1) }\n    // Sort by unit value item.v / item.w from high to low\n    items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) }\n    // Loop for greedy selection\n    var res = 0.0\n    var cap = cap\n    for item in items {\n        if item.w <= cap {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += Double(item.v)\n            cap -= item.w\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += Double(item.v) / Double(item.w) * Double(cap)\n            // No remaining capacity, so break out of the loop\n            break\n        }\n    }\n    return res\n}\n
fractional_knapsack.js
/* Item */\nclass Item {\n    constructor(w, v) {\n        this.w = w; // Item weight\n        this.v = v; // Item value\n    }\n}\n\n/* Fractional knapsack: Greedy algorithm */\nfunction fractionalKnapsack(wgt, val, cap) {\n    // Create item list with two attributes: weight, value\n    const items = wgt.map((w, i) => new Item(w, val[i]));\n    // Sort by unit value item.v / item.w from high to low\n    items.sort((a, b) => b.v / b.w - a.v / a.w);\n    // Loop for greedy selection\n    let res = 0;\n    for (const item of items) {\n        if (item.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (item.v / item.w) * cap;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    return res;\n}\n
fractional_knapsack.ts
/* Item */\nclass Item {\n    w: number; // Item weight\n    v: number; // Item value\n\n    constructor(w: number, v: number) {\n        this.w = w;\n        this.v = v;\n    }\n}\n\n/* Fractional knapsack: Greedy algorithm */\nfunction fractionalKnapsack(wgt: number[], val: number[], cap: number): number {\n    // Create item list with two attributes: weight, value\n    const items: Item[] = wgt.map((w, i) => new Item(w, val[i]));\n    // Sort by unit value item.v / item.w from high to low\n    items.sort((a, b) => b.v / b.w - a.v / a.w);\n    // Loop for greedy selection\n    let res = 0;\n    for (const item of items) {\n        if (item.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (item.v / item.w) * cap;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    return res;\n}\n
fractional_knapsack.dart
/* Item */\nclass Item {\n  int w; // Item weight\n  int v; // Item value\n\n  Item(this.w, this.v);\n}\n\n/* Fractional knapsack: Greedy algorithm */\ndouble fractionalKnapsack(List<int> wgt, List<int> val, int cap) {\n  // Create item list with two attributes: weight, value\n  List<Item> items = List.generate(wgt.length, (i) => Item(wgt[i], val[i]));\n  // Sort by unit value item.v / item.w from high to low\n  items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w));\n  // Loop for greedy selection\n  double res = 0;\n  for (Item item in items) {\n    if (item.w <= cap) {\n      // If remaining capacity is sufficient, put the entire current item into the knapsack\n      res += item.v;\n      cap -= item.w;\n    } else {\n      // If remaining capacity is insufficient, put part of the current item into the knapsack\n      res += item.v / item.w * cap;\n      // No remaining capacity, so break out of the loop\n      break;\n    }\n  }\n  return res;\n}\n
fractional_knapsack.rs
/* Item */\nstruct Item {\n    w: i32, // Item weight\n    v: i32, // Item value\n}\n\nimpl Item {\n    fn new(w: i32, v: i32) -> Self {\n        Self { w, v }\n    }\n}\n\n/* Fractional knapsack: Greedy algorithm */\nfn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 {\n    // Create item list with two attributes: weight, value\n    let mut items = wgt\n        .iter()\n        .zip(val.iter())\n        .map(|(&w, &v)| Item::new(w, v))\n        .collect::<Vec<Item>>();\n    // Sort by unit value item.v / item.w from high to low\n    items.sort_by(|a, b| {\n        (b.v as f64 / b.w as f64)\n            .partial_cmp(&(a.v as f64 / a.w as f64))\n            .unwrap()\n    });\n    // Loop for greedy selection\n    let mut res = 0.0;\n    for item in &items {\n        if item.w <= cap {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v as f64;\n            cap -= item.w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += item.v as f64 / item.w as f64 * cap as f64;\n            // No remaining capacity, so break out of the loop\n            break;\n        }\n    }\n    res\n}\n
fractional_knapsack.c
/* Item */\ntypedef struct {\n    int w; // Item weight\n    int v; // Item value\n} Item;\n\n/* Fractional knapsack: Greedy algorithm */\nfloat fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) {\n    // Create item list with two attributes: weight, value\n    Item *items = malloc(sizeof(Item) * itemCount);\n    for (int i = 0; i < itemCount; i++) {\n        items[i] = (Item){.w = wgt[i], .v = val[i]};\n    }\n    // Sort by unit value item.v / item.w from high to low\n    qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity);\n    // Loop for greedy selection\n    float res = 0.0;\n    for (int i = 0; i < itemCount; i++) {\n        if (items[i].w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += items[i].v;\n            cap -= items[i].w;\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += (float)cap / items[i].w * items[i].v;\n            cap = 0;\n            break;\n        }\n    }\n    free(items);\n    return res;\n}\n
fractional_knapsack.kt
/* Item */\nclass Item(\n    val w: Int, // Item\n    val v: Int  // Item value\n)\n\n/* Fractional knapsack: Greedy algorithm */\nfun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double {\n    // Create item list with two attributes: weight, value\n    var cap = c\n    val items = arrayOfNulls<Item>(wgt.size)\n    for (i in wgt.indices) {\n        items[i] = Item(wgt[i], _val[i])\n    }\n    // Sort by unit value item.v / item.w from high to low\n    items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) }\n    // Loop for greedy selection\n    var res = 0.0\n    for (item in items) {\n        if (item!!.w <= cap) {\n            // If remaining capacity is sufficient, put the entire current item into the knapsack\n            res += item.v\n            cap -= item.w\n        } else {\n            // If remaining capacity is insufficient, put part of the current item into the knapsack\n            res += item.v.toDouble() / item.w * cap\n            // No remaining capacity, so break out of the loop\n            break\n        }\n    }\n    return res\n}\n
fractional_knapsack.rb
### Item ###\nclass Item\n  attr_accessor :w # Item weight\n  attr_accessor :v # Item value\n\n  def initialize(w, v)\n    @w = w\n    @v = v\n  end\nend\n\n### Fractional knapsack: greedy ###\ndef fractional_knapsack(wgt, val, cap)\n  # Create item list with two attributes: weight, value\n  items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) }\n  # Sort by unit value item.v / item.w from high to low\n  items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) }\n  # Loop for greedy selection\n  res = 0\n  for item in items\n    if item.w <= cap\n      # If remaining capacity is sufficient, put the entire current item into the knapsack\n      res += item.v\n      cap -= item.w\n    else\n      # If remaining capacity is insufficient, put part of the current item into the knapsack\n      res += (item.v.to_f / item.w) * cap\n      # No remaining capacity, so break out of the loop\n      break\n    end\n  end\n  res\nend\n

The time complexity of built-in sorting algorithms is usually \\(O(\\log n)\\), and the space complexity is usually \\(O(\\log n)\\) or \\(O(n)\\), depending on the specific implementation of the programming language.

Apart from sorting, in the worst case the entire item list needs to be traversed, therefore the time complexity is \\(O(n)\\), where \\(n\\) is the number of items.

Since an Item object list is initialized, the space complexity is \\(O(n)\\).

","path":["Chapter 15. Greedy","15.2   Fractional Knapsack Problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#3-correctness-proof","level":3,"title":"3.   Correctness Proof","text":"

Using proof by contradiction. Suppose item \\(x\\) has the highest unit value, and some algorithm yields a maximum value of res, but this solution does not include item \\(x\\).

Now remove a unit weight of any item from the knapsack and replace it with a unit weight of item \\(x\\). Since item \\(x\\) has the highest unit value, the total value after replacement will definitely be greater than res. This contradicts the assumption that res is the optimal solution, proving that the optimal solution must include item \\(x\\).

For other items in this solution, we can also construct the above contradiction. In summary, items with greater unit value are always better choices, which proves that the greedy strategy is effective.

As shown in Figure 15-6, if we view item weight and item unit value as the horizontal and vertical axes of a two-dimensional chart respectively, then the fractional knapsack problem can be transformed into \"finding the maximum area enclosed within a limited horizontal axis range\". This analogy can help us understand the effectiveness of the greedy strategy from a geometric perspective.

Figure 15-6   Geometric representation of the fractional knapsack problem

","path":["Chapter 15. Greedy","15.2   Fractional Knapsack Problem"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/","level":1,"title":"15.1   Greedy Algorithm","text":"

Greedy algorithm is a common algorithm for solving optimization problems. Its basic idea is to make the seemingly best choice at each decision stage of the problem, that is, to greedily make locally optimal decisions in hopes of obtaining a globally optimal solution. Greedy algorithms are simple and efficient, and are widely applied in many practical problems.

Greedy algorithms and dynamic programming are both commonly used to solve optimization problems. They share some similarities, such as both relying on the optimal substructure property, but they work differently.

  • Dynamic programming considers all previous decisions when making the current decision, and uses solutions to past subproblems to construct the solution to the current subproblem.
  • Greedy algorithms do not consider past decisions, but instead make greedy choices moving forward, continually reducing the problem size until the problem is solved.

We will first understand how greedy algorithms work through the example problem \"coin change\". This problem has already been introduced in the \"Complete Knapsack Problem\" chapter, so I believe you are not unfamiliar with it.

Question

Given \\(n\\) types of coins, where the denomination of the \\(i\\)-th type of coin is \\(coins[i - 1]\\), and the target amount is \\(amt\\), with each type of coin available for repeated selection, what is the minimum number of coins needed to make up the target amount? If it is impossible to make up the target amount, return \\(-1\\).

The greedy strategy adopted for this problem is shown in Figure 15-1. Given a target amount, we greedily select the coin that is not greater than and closest to it, and continuously repeat this step until the target amount is reached.

Figure 15-1   Greedy strategy for coin change

The implementation code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_greedy.py
def coin_change_greedy(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: Greedy algorithm\"\"\"\n    # Assume coins list is sorted\n    i = len(coins) - 1\n    count = 0\n    # Loop to make greedy choices until no remaining amount\n    while amt > 0:\n        # Find the coin that is less than and closest to the remaining amount\n        while i > 0 and coins[i] > amt:\n            i -= 1\n        # Choose coins[i]\n        amt -= coins[i]\n        count += 1\n    # If no feasible solution is found, return -1\n    return count if amt == 0 else -1\n
coin_change_greedy.cpp
/* Coin change: Greedy algorithm */\nint coinChangeGreedy(vector<int> &coins, int amt) {\n    // Assume coins list is sorted\n    int i = coins.size() - 1;\n    int count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt == 0 ? count : -1;\n}\n
coin_change_greedy.java
/* Coin change: Greedy algorithm */\nint coinChangeGreedy(int[] coins, int amt) {\n    // Assume coins list is sorted\n    int i = coins.length - 1;\n    int count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt == 0 ? count : -1;\n}\n
coin_change_greedy.cs
/* Coin change: Greedy algorithm */\nint CoinChangeGreedy(int[] coins, int amt) {\n    // Assume coins list is sorted\n    int i = coins.Length - 1;\n    int count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt == 0 ? count : -1;\n}\n
coin_change_greedy.go
/* Coin change: Greedy algorithm */\nfunc coinChangeGreedy(coins []int, amt int) int {\n    // Assume coins list is sorted\n    i := len(coins) - 1\n    count := 0\n    // Loop to make greedy choices until no remaining amount\n    for amt > 0 {\n        // Find the coin that is less than and closest to the remaining amount\n        for i > 0 && coins[i] > amt {\n            i--\n        }\n        // Choose coins[i]\n        amt -= coins[i]\n        count++\n    }\n    // If no feasible solution is found, return -1\n    if amt != 0 {\n        return -1\n    }\n    return count\n}\n
coin_change_greedy.swift
/* Coin change: Greedy algorithm */\nfunc coinChangeGreedy(coins: [Int], amt: Int) -> Int {\n    // Assume coins list is sorted\n    var i = coins.count - 1\n    var count = 0\n    var amt = amt\n    // Loop to make greedy choices until no remaining amount\n    while amt > 0 {\n        // Find the coin that is less than and closest to the remaining amount\n        while i > 0 && coins[i] > amt {\n            i -= 1\n        }\n        // Choose coins[i]\n        amt -= coins[i]\n        count += 1\n    }\n    // If no feasible solution is found, return -1\n    return amt == 0 ? count : -1\n}\n
coin_change_greedy.js
/* Coin change: Greedy algorithm */\nfunction coinChangeGreedy(coins, amt) {\n    // Assume coins array is sorted\n    let i = coins.length - 1;\n    let count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt === 0 ? count : -1;\n}\n
coin_change_greedy.ts
/* Coin change: Greedy algorithm */\nfunction coinChangeGreedy(coins: number[], amt: number): number {\n    // Assume coins array is sorted\n    let i = coins.length - 1;\n    let count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt === 0 ? count : -1;\n}\n
coin_change_greedy.dart
/* Coin change: Greedy algorithm */\nint coinChangeGreedy(List<int> coins, int amt) {\n  // Assume coins list is sorted\n  int i = coins.length - 1;\n  int count = 0;\n  // Loop to make greedy choices until no remaining amount\n  while (amt > 0) {\n    // Find the coin that is less than and closest to the remaining amount\n    while (i > 0 && coins[i] > amt) {\n      i--;\n    }\n    // Choose coins[i]\n    amt -= coins[i];\n    count++;\n  }\n  // If no feasible solution is found, return -1\n  return amt == 0 ? count : -1;\n}\n
coin_change_greedy.rs
/* Coin change: Greedy algorithm */\nfn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 {\n    // Assume coins list is sorted\n    let mut i = coins.len() - 1;\n    let mut count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while amt > 0 {\n        // Find the coin that is less than and closest to the remaining amount\n        while i > 0 && coins[i] > amt {\n            i -= 1;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count += 1;\n    }\n    // If no feasible solution is found, return -1\n    if amt == 0 {\n        count\n    } else {\n        -1\n    }\n}\n
coin_change_greedy.c
/* Coin change: Greedy algorithm */\nint coinChangeGreedy(int *coins, int size, int amt) {\n    // Assume coins list is sorted\n    int i = size - 1;\n    int count = 0;\n    // Loop to make greedy choices until no remaining amount\n    while (amt > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // If no feasible solution is found, return -1\n    return amt == 0 ? count : -1;\n}\n
coin_change_greedy.kt
/* Coin change: Greedy algorithm */\nfun coinChangeGreedy(coins: IntArray, amt: Int): Int {\n    // Assume coins list is sorted\n    var am = amt\n    var i = coins.size - 1\n    var count = 0\n    // Loop to make greedy choices until no remaining amount\n    while (am > 0) {\n        // Find the coin that is less than and closest to the remaining amount\n        while (i > 0 && coins[i] > am) {\n            i--\n        }\n        // Choose coins[i]\n        am -= coins[i]\n        count++\n    }\n    // If no feasible solution is found, return -1\n    return if (am == 0) count else -1\n}\n
coin_change_greedy.rb
### Coin change: greedy ###\ndef coin_change_greedy(coins, amt)\n  # Assume coins list is sorted\n  i = coins.length - 1\n  count = 0\n  # Loop to make greedy choices until no remaining amount\n  while amt > 0\n    # Find the coin that is less than and closest to the remaining amount\n    while i > 0 && coins[i] > amt\n      i -= 1\n    end\n    # Choose coins[i]\n    amt -= coins[i]\n    count += 1\n  end\n  # Return -1 if no solution found\n  amt == 0 ? count : -1\nend\n

You might exclaim: So clean! The greedy algorithm solves the coin change problem in about ten lines of code.

","path":["Chapter 15. Greedy","15.1   Greedy Algorithm"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1511-advantages-and-limitations-of-greedy-algorithms","level":2,"title":"15.1.1   Advantages and Limitations of Greedy Algorithms","text":"

Greedy algorithms are not only straightforward and simple to implement, but are also usually very efficient. In the code above, if the smallest coin denomination is \\(\\min(coins)\\), the greedy choice loops at most \\(amt / \\min(coins)\\) times, giving a time complexity of \\(O(amt / \\min(coins))\\). This is an order of magnitude smaller than the time complexity of the dynamic programming solution \\(O(n \\times amt)\\).

However, for certain coin denomination combinations, greedy algorithms cannot find the optimal solution. Figure 15-2 provides two examples.

  • Positive example \\(coins = [1, 5, 10, 20, 50, 100]\\): With this coin combination, given any \\(amt\\), the greedy algorithm can find the optimal solution.
  • Negative example \\(coins = [1, 20, 50]\\): Suppose \\(amt = 60\\), the greedy algorithm can only find the combination \\(50 + 1 \\times 10\\), totaling \\(11\\) coins, but dynamic programming can find the optimal solution \\(20 + 20 + 20\\), requiring only \\(3\\) coins.
  • Negative example \\(coins = [1, 49, 50]\\): Suppose \\(amt = 98\\), the greedy algorithm can only find the combination \\(50 + 1 \\times 48\\), totaling \\(49\\) coins, but dynamic programming can find the optimal solution \\(49 + 49\\), requiring only \\(2\\) coins.

Figure 15-2   Examples where greedy algorithms cannot find the optimal solution

In other words, for the coin change problem, greedy algorithms cannot guarantee finding the global optimal solution, and may even find very poor solutions. It is better suited for solving with dynamic programming.

Generally, the applicability of greedy algorithms falls into the following two situations.

  1. Can guarantee finding the optimal solution: In this situation, greedy algorithms are often the best choice, because they tend to be more efficient than backtracking and dynamic programming.
  2. Can find an approximate optimal solution: Greedy algorithms are also applicable in this situation. For many complex problems, finding the global optimal solution is very difficult, and being able to find a suboptimal solution with high efficiency is also very good.
","path":["Chapter 15. Greedy","15.1   Greedy Algorithm"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1512-characteristics-of-greedy-algorithms","level":2,"title":"15.1.2   Characteristics of Greedy Algorithms","text":"

So the question arises: what kind of problems are suitable for solving with greedy algorithms? Or in other words, under what conditions can greedy algorithms guarantee finding the optimal solution?

Compared to dynamic programming, the conditions for using greedy algorithms are stricter, mainly focusing on two properties of the problem.

  • Greedy choice property: Only when locally optimal choices can always lead to a globally optimal solution can greedy algorithms guarantee obtaining the optimal solution.
  • Optimal substructure: The optimal solution to the original problem contains the optimal solutions to subproblems.

Optimal substructure has already been introduced in the \"Dynamic Programming\" chapter, so we won't elaborate on it here. It's worth noting that the optimal substructure of some problems is not obvious, but they can still be solved using greedy algorithms.

We mainly explore methods for determining the greedy choice property. Although its description seems relatively simple, in practice, for many problems, proving the greedy choice property is not easy.

For example, in the coin change problem, although we can easily provide counterexamples to disprove the greedy choice property, proving it is quite difficult. If asked: what conditions must a coin combination satisfy to be solvable using a greedy algorithm? We often can only rely on intuition or examples to give an ambiguous answer, and find it difficult to provide a rigorous mathematical proof.

Quote

There is a paper that presents an algorithm with \\(O(n^3)\\) time complexity for determining whether a coin combination can use a greedy algorithm to find the optimal solution for any amount.

Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234.

","path":["Chapter 15. Greedy","15.1   Greedy Algorithm"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1513-steps-for-solving-problems-with-greedy-algorithms","level":2,"title":"15.1.3   Steps for Solving Problems with Greedy Algorithms","text":"

The problem-solving process for greedy problems can generally be divided into the following three steps.

  1. Problem analysis: Sort out and understand the problem characteristics, including state definition, optimization objectives, and constraints, etc. This step is also involved in backtracking and dynamic programming.
  2. Determine the greedy strategy: Determine how to make greedy choices at each step. This strategy should be able to reduce the problem size at each step, ultimately solving the entire problem.
  3. Correctness proof: It is usually necessary to prove that the problem has both greedy choice property and optimal substructure. This step may require mathematical proofs, such as mathematical induction or proof by contradiction.

Determining the greedy strategy is the core step in solving the problem, but it may not be easy to implement, mainly for the following reasons.

  • Greedy strategies differ greatly between different problems. For many problems, the greedy strategy is relatively straightforward, and we can derive it through some general thinking and attempts. However, for some complex problems, the greedy strategy may be very elusive, which really tests one's problem-solving experience and algorithmic ability.
  • Some greedy strategies are highly misleading. When we confidently design a greedy strategy, write the solution code and submit it for testing, we may find that some test cases cannot pass. This is because the designed greedy strategy is only \"partially correct\", as exemplified by the coin change problem discussed above.

To ensure correctness, we should rigorously mathematically prove the greedy strategy, usually using proof by contradiction or mathematical induction.

However, correctness proofs may also not be easy. If we have no clue, we usually choose to debug the code based on test cases, step by step modifying and verifying the greedy strategy.

","path":["Chapter 15. Greedy","15.1   Greedy Algorithm"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1514-typical-problems-solved-by-greedy-algorithms","level":2,"title":"15.1.4   Typical Problems Solved by Greedy Algorithms","text":"

Greedy algorithms are often applied to optimization problems that satisfy greedy choice property and optimal substructure. Below are some typical greedy algorithm problems.

  • Coin change problem: With certain coin combinations, greedy algorithms can always obtain the optimal solution.
  • Interval scheduling problem: Suppose you have some tasks, each taking place during a period of time, and your goal is to complete as many tasks as possible. If you always choose the task that ends earliest, then the greedy algorithm can obtain the optimal solution.
  • Fractional knapsack problem: Given a set of items and a carrying capacity, your goal is to select a set of items such that the total weight does not exceed the carrying capacity and the total value is maximized. If you always choose the item with the highest value-to-weight ratio (value / weight), then the greedy algorithm can obtain the optimal solution in some cases.
  • Stock trading problem: Given a set of historical stock prices, you can make multiple trades, but if you already hold stocks, you cannot buy again before selling, and the goal is to obtain the maximum profit.
  • Huffman coding: Huffman coding is a greedy algorithm used for lossless data compression. By constructing a Huffman tree and always merging the two nodes with the lowest frequency, the resulting Huffman tree has the minimum weighted path length (encoding length).
  • Dijkstra's algorithm: It is a greedy algorithm for solving the shortest path problem from a given source vertex to all other vertices.
","path":["Chapter 15. Greedy","15.1   Greedy Algorithm"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/","level":1,"title":"15.3   Max Capacity Problem","text":"

Question

Input an array \\(ht\\), where each element represents the height of a vertical partition. Any two partitions in the array, along with the space between them, can form a container.

The capacity of the container equals the product of height and width (area), where the height is determined by the shorter partition, and the width is the difference in array indices between the two partitions.

Please select two partitions in the array such that the capacity of the formed container is maximized, and return the maximum capacity. An example is shown in Figure 15-7.

Figure 15-7   Example data for the max capacity problem

The container is formed by any two partitions, therefore the state of this problem is the indices of two partitions, denoted as \\([i, j]\\).

According to the problem description, capacity equals height multiplied by width, where height is determined by the shorter partition, and width is the difference in array indices between the two partitions. Let the capacity be \\(cap[i, j]\\), then the calculation formula is:

\\[ cap[i, j] = \\min(ht[i], ht[j]) \\times (j - i) \\]

Let the array length be \\(n\\), then the number of combinations of two partitions (total number of states) is \\(C_n^2 = \\frac{n(n - 1)}{2}\\). Most directly, we can exhaustively enumerate all states to find the maximum capacity, with time complexity \\(O(n^2)\\).

","path":["Chapter 15. Greedy","15.3   Max Capacity Problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#1-greedy-strategy-determination","level":3,"title":"1.   Greedy Strategy Determination","text":"

This problem has a more efficient solution. As shown in Figure 15-8, select a state \\([i, j]\\) where index \\(i < j\\) and height \\(ht[i] < ht[j]\\), meaning \\(i\\) is the short partition and \\(j\\) is the long partition.

Figure 15-8   Initial state

As shown in Figure 15-9, if we now move the long partition \\(j\\) closer to the short partition \\(i\\), the capacity will definitely decrease.

This is because after moving the long partition \\(j\\), the width \\(j-i\\) definitely decreases; and since height is determined by the short partition, the height can only remain unchanged (\\(i\\) is still the short partition) or decrease (the moved \\(j\\) becomes the short partition).

Figure 15-9   State after moving the long partition inward

Conversely, we can only possibly increase capacity by contracting the short partition \\(i\\) inward. Because although width will definitely decrease, height may increase (the moved short partition \\(i\\) may become taller). For example, in Figure 15-10, the area increases after moving the short partition.

Figure 15-10   State after moving the short partition inward

From this we can derive the greedy strategy for this problem: initialize two pointers at both ends of the container, and in each round contract the pointer corresponding to the short partition inward, until the two pointers meet.

Figure 15-11 shows the execution process of the greedy strategy.

  1. In the initial state, pointers \\(i\\) and \\(j\\) are at both ends of the array.
  2. Calculate the capacity of the current state \\(cap[i, j]\\), and update the maximum capacity.
  3. Compare the heights of partition \\(i\\) and partition \\(j\\), and move the short partition inward by one position.
  4. Loop through steps 2. and 3. until \\(i\\) and \\(j\\) meet.
<1><2><3><4><5><6><7><8><9>

Figure 15-11   Greedy process for the max capacity problem

","path":["Chapter 15. Greedy","15.3   Max Capacity Problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"

The code loops at most \\(n\\) rounds, therefore the time complexity is \\(O(n)\\).

Variables \\(i\\), \\(j\\), and \\(res\\) use a constant amount of extra space, therefore the space complexity is \\(O(1)\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby max_capacity.py
def max_capacity(ht: list[int]) -> int:\n    \"\"\"Max capacity: Greedy algorithm\"\"\"\n    # Initialize i, j to be at both ends of the array\n    i, j = 0, len(ht) - 1\n    # Initial max capacity is 0\n    res = 0\n    # Loop for greedy selection until the two boards meet\n    while i < j:\n        # Update max capacity\n        cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        # Move the shorter board inward\n        if ht[i] < ht[j]:\n            i += 1\n        else:\n            j -= 1\n    return res\n
max_capacity.cpp
/* Max capacity: Greedy algorithm */\nint maxCapacity(vector<int> &ht) {\n    // Initialize i, j to be at both ends of the array\n    int i = 0, j = ht.size() - 1;\n    // Initial max capacity is 0\n    int res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        int cap = min(ht[i], ht[j]) * (j - i);\n        res = max(res, cap);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
max_capacity.java
/* Max capacity: Greedy algorithm */\nint maxCapacity(int[] ht) {\n    // Initialize i, j to be at both ends of the array\n    int i = 0, j = ht.length - 1;\n    // Initial max capacity is 0\n    int res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        int cap = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
max_capacity.cs
/* Max capacity: Greedy algorithm */\nint MaxCapacity(int[] ht) {\n    // Initialize i, j to be at both ends of the array\n    int i = 0, j = ht.Length - 1;\n    // Initial max capacity is 0\n    int res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        int cap = Math.Min(ht[i], ht[j]) * (j - i);\n        res = Math.Max(res, cap);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
max_capacity.go
/* Max capacity: Greedy algorithm */\nfunc maxCapacity(ht []int) int {\n    // Initialize i, j to be at both ends of the array\n    i, j := 0, len(ht)-1\n    // Initial max capacity is 0\n    res := 0\n    // Loop for greedy selection until the two boards meet\n    for i < j {\n        // Update max capacity\n        capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i)\n        res = int(math.Max(float64(res), float64(capacity)))\n        // Move the shorter board inward\n        if ht[i] < ht[j] {\n            i++\n        } else {\n            j--\n        }\n    }\n    return res\n}\n
max_capacity.swift
/* Max capacity: Greedy algorithm */\nfunc maxCapacity(ht: [Int]) -> Int {\n    // Initialize i, j to be at both ends of the array\n    var i = ht.startIndex, j = ht.endIndex - 1\n    // Initial max capacity is 0\n    var res = 0\n    // Loop for greedy selection until the two boards meet\n    while i < j {\n        // Update max capacity\n        let cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        // Move the shorter board inward\n        if ht[i] < ht[j] {\n            i += 1\n        } else {\n            j -= 1\n        }\n    }\n    return res\n}\n
max_capacity.js
/* Max capacity: Greedy algorithm */\nfunction maxCapacity(ht) {\n    // Initialize i, j to be at both ends of the array\n    let i = 0,\n        j = ht.length - 1;\n    // Initial max capacity is 0\n    let res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        const cap = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    return res;\n}\n
max_capacity.ts
/* Max capacity: Greedy algorithm */\nfunction maxCapacity(ht: number[]): number {\n    // Initialize i, j to be at both ends of the array\n    let i = 0,\n        j = ht.length - 1;\n    // Initial max capacity is 0\n    let res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        const cap: number = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    return res;\n}\n
max_capacity.dart
/* Max capacity: Greedy algorithm */\nint maxCapacity(List<int> ht) {\n  // Initialize i, j to be at both ends of the array\n  int i = 0, j = ht.length - 1;\n  // Initial max capacity is 0\n  int res = 0;\n  // Loop for greedy selection until the two boards meet\n  while (i < j) {\n    // Update max capacity\n    int cap = min(ht[i], ht[j]) * (j - i);\n    res = max(res, cap);\n    // Move the shorter board inward\n    if (ht[i] < ht[j]) {\n      i++;\n    } else {\n      j--;\n    }\n  }\n  return res;\n}\n
max_capacity.rs
/* Max capacity: Greedy algorithm */\nfn max_capacity(ht: &[i32]) -> i32 {\n    // Initialize i, j to be at both ends of the array\n    let mut i = 0;\n    let mut j = ht.len() - 1;\n    // Initial max capacity is 0\n    let mut res = 0;\n    // Loop for greedy selection until the two boards meet\n    while i < j {\n        // Update max capacity\n        let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32;\n        res = std::cmp::max(res, cap);\n        // Move the shorter board inward\n        if ht[i] < ht[j] {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    res\n}\n
max_capacity.c
/* Max capacity: Greedy algorithm */\nint maxCapacity(int ht[], int htLength) {\n    // Initialize i, j to be at both ends of the array\n    int i = 0;\n    int j = htLength - 1;\n    // Initial max capacity is 0\n    int res = 0;\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        int capacity = myMin(ht[i], ht[j]) * (j - i);\n        res = myMax(res, capacity);\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
max_capacity.kt
/* Max capacity: Greedy algorithm */\nfun maxCapacity(ht: IntArray): Int {\n    // Initialize i, j to be at both ends of the array\n    var i = 0\n    var j = ht.size - 1\n    // Initial max capacity is 0\n    var res = 0\n    // Loop for greedy selection until the two boards meet\n    while (i < j) {\n        // Update max capacity\n        val cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        // Move the shorter board inward\n        if (ht[i] < ht[j]) {\n            i++\n        } else {\n            j--\n        }\n    }\n    return res\n}\n
max_capacity.rb
### Maximum capacity: greedy ###\ndef max_capacity(ht)\n  # Initialize i, j to be at both ends of the array\n  i, j = 0, ht.length - 1\n  # Initial max capacity is 0\n  res = 0\n\n  # Loop for greedy selection until the two boards meet\n  while i < j\n    # Update max capacity\n    cap = [ht[i], ht[j]].min * (j - i)\n    res = [res, cap].max\n    # Move the shorter board inward\n    if ht[i] < ht[j]\n      i += 1\n    else\n      j -= 1\n    end\n  end\n\n  res\nend\n
","path":["Chapter 15. Greedy","15.3   Max Capacity Problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#3-correctness-proof","level":3,"title":"3.   Correctness Proof","text":"

The reason greedy is faster than exhaustive enumeration is that each round of greedy selection \"skips\" some states.

For example, in state \\(cap[i, j]\\) where \\(i\\) is the short partition and \\(j\\) is the long partition, if we greedily move the short partition \\(i\\) inward by one position, the states shown in Figure 15-12 will be \"skipped\". This means that the capacities of these states cannot be verified later.

\\[ cap[i, i+1], cap[i, i+2], \\dots, cap[i, j-2], cap[i, j-1] \\]

Figure 15-12   States skipped by moving the short partition

Observing carefully, these skipped states are actually all the states obtained by moving the long partition \\(j\\) inward. We have already proven that moving the long partition inward will definitely decrease capacity. That is, the skipped states cannot possibly be the optimal solution, skipping them will not cause us to miss the optimal solution.

The above analysis shows that the operation of moving the short partition is \"safe\", and the greedy strategy is effective.

","path":["Chapter 15. Greedy","15.3   Max Capacity Problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/","level":1,"title":"15.4   Max Product Cutting Problem","text":"

Question

Given a positive integer \\(n\\), split it into the sum of at least two positive integers, and find the maximum product of all integers after splitting, as shown in Figure 15-13.

Figure 15-13   Problem definition of max product cutting

Suppose we split \\(n\\) into \\(m\\) integer factors, where the \\(i\\)-th factor is denoted as \\(n_i\\), that is

\\[ n = \\sum_{i=1}^{m}n_i \\]

The goal of this problem is to find the maximum product of all integer factors, namely

\\[ \\max(\\prod_{i=1}^{m}n_i) \\]

We need to think about: how large should the splitting count \\(m\\) be, and what should each \\(n_i\\) be?

","path":["Chapter 15. Greedy","15.4   Max Product Cutting Problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#1-greedy-strategy-determination","level":3,"title":"1.   Greedy Strategy Determination","text":"

Based on experience, the product of two integers is often greater than their sum. Suppose we split out a factor of \\(2\\) from \\(n\\), then their product is \\(2(n-2)\\). We compare this product with \\(n\\):

\\[ \\begin{aligned} 2(n-2) & \\geq n \\newline 2n - n - 4 & \\geq 0 \\newline n & \\geq 4 \\end{aligned} \\]

As shown in Figure 15-14, when \\(n \\geq 4\\), splitting out a \\(2\\) will increase the product, which indicates that integers greater than or equal to \\(4\\) should all be split.

Greedy strategy one: If the splitting scheme includes factors \\(\\geq 4\\), then they should continue to be split. The final splitting scheme should only contain factors \\(1\\), \\(2\\), and \\(3\\).

Figure 15-14   Splitting causes product to increase

Next, consider which factor is optimal. Among the three factors \\(1\\), \\(2\\), and \\(3\\), clearly \\(1\\) is the worst, because \\(1 \\times (n-1) < n\\) always holds, meaning splitting out \\(1\\) will actually decrease the product.

As shown in Figure 15-15, when \\(n = 6\\), we have \\(3 \\times 3 > 2 \\times 2 \\times 2\\). This means that splitting out \\(3\\) is better than splitting out \\(2\\).

Greedy strategy two: In the splitting scheme, there should be at most two \\(2\\)s. Because three \\(2\\)s can always be replaced by two \\(3\\)s to obtain a larger product.

Figure 15-15   Optimal splitting factor

In summary, the following greedy strategies can be derived.

  1. Input integer \\(n\\), continuously split out factor \\(3\\) until the remainder is \\(0\\), \\(1\\), or \\(2\\).
  2. When the remainder is \\(0\\), it means \\(n\\) is a multiple of \\(3\\), so no further action is needed.
  3. When the remainder is \\(2\\), do not continue splitting, keep it.
  4. When the remainder is \\(1\\), since \\(2 \\times 2 > 1 \\times 3\\), the last \\(3\\) should be replaced with \\(2\\).
","path":["Chapter 15. Greedy","15.4   Max Product Cutting Problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#2-code-implementation","level":3,"title":"2.   Code Implementation","text":"

As shown in Figure 15-16, we don't need to use loops to split the integer, but can use integer division to get the count of \\(3\\)s as \\(a\\), and modulo operation to get the remainder as \\(b\\), at which point we have:

\\[ n = 3 a + b \\]

Please note that for the edge case of \\(n \\leq 3\\), a \\(1\\) must be split out, with product \\(1 \\times (n - 1)\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby max_product_cutting.py
def max_product_cutting(n: int) -> int:\n    \"\"\"Max product cutting: Greedy algorithm\"\"\"\n    # When n <= 3, must cut out a 1\n    if n <= 3:\n        return 1 * (n - 1)\n    # Greedily cut out 3, a is the number of 3s, b is the remainder\n    a, b = n // 3, n % 3\n    if b == 1:\n        # When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return int(math.pow(3, a - 1)) * 2 * 2\n    if b == 2:\n        # When the remainder is 2, do nothing\n        return int(math.pow(3, a)) * 2\n    # When the remainder is 0, do nothing\n    return int(math.pow(3, a))\n
max_product_cutting.cpp
/* Max product cutting: Greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return (int)pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, do nothing\n        return (int)pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return (int)pow(3, a);\n}\n
max_product_cutting.java
/* Max product cutting: Greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return (int) Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, do nothing\n        return (int) Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return (int) Math.pow(3, a);\n}\n
max_product_cutting.cs
/* Max product cutting: Greedy algorithm */\nint MaxProductCutting(int n) {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return (int)Math.Pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, do nothing\n        return (int)Math.Pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return (int)Math.Pow(3, a);\n}\n
max_product_cutting.go
/* Max product cutting: Greedy algorithm */\nfunc maxProductCutting(n int) int {\n    // When n <= 3, must cut out a 1\n    if n <= 3 {\n        return 1 * (n - 1)\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    a := n / 3\n    b := n % 3\n    if b == 1 {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return int(math.Pow(3, float64(a-1))) * 2 * 2\n    }\n    if b == 2 {\n        // When the remainder is 2, do nothing\n        return int(math.Pow(3, float64(a))) * 2\n    }\n    // When the remainder is 0, do nothing\n    return int(math.Pow(3, float64(a)))\n}\n
max_product_cutting.swift
/* Max product cutting: Greedy algorithm */\nfunc maxProductCutting(n: Int) -> Int {\n    // When n <= 3, must cut out a 1\n    if n <= 3 {\n        return 1 * (n - 1)\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    let a = n / 3\n    let b = n % 3\n    if b == 1 {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return pow(3, a - 1) * 2 * 2\n    }\n    if b == 2 {\n        // When the remainder is 2, do nothing\n        return pow(3, a) * 2\n    }\n    // When the remainder is 0, do nothing\n    return pow(3, a)\n}\n
max_product_cutting.js
/* Max product cutting: Greedy algorithm */\nfunction maxProductCutting(n) {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    let a = Math.floor(n / 3);\n    let b = n % 3;\n    if (b === 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b === 2) {\n        // When the remainder is 2, do nothing\n        return Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return Math.pow(3, a);\n}\n
max_product_cutting.ts
/* Max product cutting: Greedy algorithm */\nfunction maxProductCutting(n: number): number {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    let a: number = Math.floor(n / 3);\n    let b: number = n % 3;\n    if (b === 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b === 2) {\n        // When the remainder is 2, do nothing\n        return Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return Math.pow(3, a);\n}\n
max_product_cutting.dart
/* Max product cutting: Greedy algorithm */\nint maxProductCutting(int n) {\n  // When n <= 3, must cut out a 1\n  if (n <= 3) {\n    return 1 * (n - 1);\n  }\n  // Greedily cut out 3, a is the number of 3s, b is the remainder\n  int a = n ~/ 3;\n  int b = n % 3;\n  if (b == 1) {\n    // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n    return (pow(3, a - 1) * 2 * 2).toInt();\n  }\n  if (b == 2) {\n    // When the remainder is 2, do nothing\n    return (pow(3, a) * 2).toInt();\n  }\n  // When the remainder is 0, do nothing\n  return pow(3, a).toInt();\n}\n
max_product_cutting.rs
/* Max product cutting: Greedy algorithm */\nfn max_product_cutting(n: i32) -> i32 {\n    // When n <= 3, must cut out a 1\n    if n <= 3 {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    let a = n / 3;\n    let b = n % 3;\n    if b == 1 {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        3_i32.pow(a as u32 - 1) * 2 * 2\n    } else if b == 2 {\n        // When the remainder is 2, do nothing\n        3_i32.pow(a as u32) * 2\n    } else {\n        // When the remainder is 0, do nothing\n        3_i32.pow(a as u32)\n    }\n}\n
max_product_cutting.c
/* Max product cutting: Greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, do nothing\n        return pow(3, a) * 2;\n    }\n    // When the remainder is 0, do nothing\n    return pow(3, a);\n}\n
max_product_cutting.kt
/* Max product cutting: Greedy algorithm */\nfun maxProductCutting(n: Int): Int {\n    // When n <= 3, must cut out a 1\n    if (n <= 3) {\n        return 1 * (n - 1)\n    }\n    // Greedily cut out 3, a is the number of 3s, b is the remainder\n    val a = n / 3\n    val b = n % 3\n    if (b == 1) {\n        // When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n        return 3.0.pow((a - 1)).toInt() * 2 * 2\n    }\n    if (b == 2) {\n        // When the remainder is 2, do nothing\n        return 3.0.pow(a).toInt() * 2 * 2\n    }\n    // When the remainder is 0, do nothing\n    return 3.0.pow(a).toInt()\n}\n
max_product_cutting.rb
### Maximum cutting product: greedy ###\ndef max_product_cutting(n)\n  # When n <= 3, must cut out a 1\n  return 1 * (n - 1) if n <= 3\n  # Greedily cut out 3, a is the number of 3s, b is the remainder\n  a, b = n / 3, n % 3\n  # When the remainder is 1, convert a pair of 1 * 3 to 2 * 2\n  return (3.pow(a - 1) * 2 * 2).to_i if b == 1\n  # When the remainder is 2, do nothing\n  return (3.pow(a) * 2).to_i if b == 2\n  # When the remainder is 0, do nothing\n  3.pow(a).to_i\nend\n

Figure 15-16   Calculation method for max product cutting

The time complexity depends on the implementation of the exponentiation operation in the programming language. Taking Python as an example, there are three commonly used power calculation functions.

  • Both the operator ** and the function pow() have time complexity \\(O(\\log⁡ a)\\).
  • The function math.pow() internally calls the C library's pow() function, which performs floating-point exponentiation, with time complexity \\(O(1)\\).

Variables \\(a\\) and \\(b\\) use a constant amount of extra space, therefore the space complexity is \\(O(1)\\).

","path":["Chapter 15. Greedy","15.4   Max Product Cutting Problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#3-correctness-proof","level":3,"title":"3.   Correctness Proof","text":"

Using proof by contradiction, only analyzing the case where \\(n \\geq 4\\).

  1. All factors \\(\\leq 3\\): Suppose the optimal splitting scheme includes a factor \\(x \\geq 4\\), then it can definitely continue to be split into \\(2(x-2)\\) to obtain a larger (or equal) product. This contradicts the assumption.
  2. The splitting scheme does not contain \\(1\\): Suppose the optimal splitting scheme includes a factor of \\(1\\), then it can definitely be merged into another factor to obtain a larger product. This contradicts the assumption.
  3. The splitting scheme contains at most two \\(2\\)s: Suppose the optimal splitting scheme includes three \\(2\\)s, then they can definitely be replaced by two \\(3\\)s for a larger product. This contradicts the assumption.
","path":["Chapter 15. Greedy","15.4   Max Product Cutting Problem"],"tags":[]},{"location":"chapter_greedy/summary/","level":1,"title":"15.5   Summary","text":"","path":["Chapter 15. Greedy","15.5   Summary"],"tags":[]},{"location":"chapter_greedy/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Greedy algorithms are typically used to solve optimization problems. The principle is to make locally optimal decisions at each decision stage in hopes of obtaining a globally optimal solution.
  • Greedy algorithms iteratively make one greedy choice after another, transforming the problem into a smaller subproblem in each round, until the problem is solved.
  • Greedy algorithms are not only simple to implement, but also have high problem-solving efficiency. Compared to dynamic programming, greedy algorithms typically have lower time complexity.
  • In the coin change problem, for certain coin combinations, greedy algorithms can guarantee finding the optimal solution; for other coin combinations, however, greedy algorithms may find very poor solutions.
  • Problems suitable for solving with greedy algorithms have two major properties: greedy choice property and optimal substructure. The greedy choice property represents the effectiveness of the greedy strategy.
  • For some complex problems, proving the greedy choice property is not simple. Relatively speaking, disproving it is easier, such as in the coin change problem.
  • Solving greedy problems mainly consists of three steps: problem analysis, determining the greedy strategy, and correctness proof. Among these, determining the greedy strategy is the core step, and correctness proof is often the difficult point.
  • The fractional knapsack problem, based on the 0-1 knapsack problem, allows selecting a portion of items, and therefore can be solved using greedy algorithms. The correctness of the greedy strategy can be proven using proof by contradiction.
  • The max capacity problem can be solved using exhaustive enumeration with time complexity \\(O(n^2)\\). By designing a greedy strategy to move the short partition inward in each round, the time complexity can be optimized to \\(O(n)\\).
  • In the max product cutting problem, we successively derive two greedy strategies: integers \\(\\geq 4\\) should all continue to be split, and the optimal splitting factor is \\(3\\). The code includes exponentiation operations, and the time complexity depends on the implementation method of exponentiation, typically being \\(O(1)\\) or \\(O(\\log n)\\).
","path":["Chapter 15. Greedy","15.5   Summary"],"tags":[]},{"location":"chapter_hashing/","level":1,"title":"Chapter 6.   Hashing","text":"

Abstract

In the world of computing, a hash table is like a clever librarian.

They know how to calculate call numbers, enabling them to quickly locate the target book.

","path":["Chapter 6. Hashing","Chapter 6.   Hashing"],"tags":[]},{"location":"chapter_hashing/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 6.1   Hash Table
  • 6.2   Hash Collision
  • 6.3   Hash Algorithm
  • 6.4   Summary
","path":["Chapter 6. Hashing","Chapter 6.   Hashing"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/","level":1,"title":"6.3   Hash Algorithm","text":"

The previous two sections introduced the working principle of hash tables and the methods to handle hash collisions. However, both open addressing and separate chaining can only ensure that the hash table functions normally when hash collisions occur, but cannot reduce the frequency of hash collisions.

If hash collisions occur too frequently, the performance of the hash table will deteriorate drastically. As shown in Figure 6-8, for a separate chaining hash table, in the ideal case, the key-value pairs are evenly distributed across the buckets, achieving optimal query efficiency; in the worst case, all key-value pairs are stored in the same bucket, degrading the time complexity to \\(O(n)\\).

Figure 6-8   Ideal and worst cases of hash collisions

The distribution of key-value pairs is determined by the hash function. Recalling the calculation steps of the hash function, first compute the hash value, then take the modulo by the array length:

index = hash(key) % capacity\n

Observing the above formula, when the hash table capacity capacity is fixed, the hash algorithm hash() determines the output value, thereby determining the distribution of key-value pairs in the hash table.

This means that, to reduce the probability of hash collisions, we should focus on the design of the hash algorithm hash().

","path":["Chapter 6. Hashing","6.3   Hash Algorithm"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#631-goals-of-hash-algorithms","level":2,"title":"6.3.1   Goals of Hash Algorithms","text":"

To achieve a \"fast and stable\" hash table data structure, hash algorithms should have the following characteristics:

  • Determinism: For the same input, the hash algorithm should always produce the same output. Only then can the hash table be reliable.
  • High efficiency: The process of computing the hash value should be fast enough. The smaller the computational overhead, the more practical the hash table.
  • Uniform distribution: The hash algorithm should ensure that key-value pairs are evenly distributed in the hash table. The more uniform the distribution, the lower the probability of hash collisions.

In fact, hash algorithms are not only used to implement hash tables but are also widely applied in other fields.

  • Password storage: To protect the security of user passwords, systems usually do not store the plaintext passwords but rather the hash values of the passwords. When a user enters a password, the system calculates the hash value of the input and compares it with the stored hash value. If they match, the password is considered correct.
  • Data integrity check: The data sender can calculate the hash value of the data and send it along; the receiver can recalculate the hash value of the received data and compare it with the received hash value. If they match, the data is considered intact.

For cryptographic applications, to prevent reverse engineering such as deducing the original password from the hash value, hash algorithms need higher-level security features.

  • Unidirectionality: It should be impossible to deduce any information about the input data from the hash value.
  • Collision resistance: It should be extremely difficult to find two different inputs that produce the same hash value.
  • Avalanche effect: Minor changes in the input should lead to significant and unpredictable changes in the output.

Note that \"uniform distribution\" and \"collision resistance\" are two independent concepts. Satisfying uniform distribution does not necessarily mean collision resistance. For example, under random input key, the hash function key % 100 can produce a uniformly distributed output. However, this hash algorithm is too simple, and all key with the same last two digits will have the same output, making it easy to deduce a usable key from the hash value, thereby cracking the password.

","path":["Chapter 6. Hashing","6.3   Hash Algorithm"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#632-design-of-hash-algorithms","level":2,"title":"6.3.2   Design of Hash Algorithms","text":"

The design of hash algorithms is a complex issue that requires consideration of many factors. However, for some less demanding scenarios, we can also design some simple hash algorithms.

  • Additive hash: Add up the ASCII codes of each character in the input and use the total sum as the hash value.
  • Multiplicative hash: Utilize the non-correlation of multiplication, multiplying each round by a constant, accumulating the ASCII codes of each character into the hash value.
  • XOR hash: Accumulate the hash value by XORing each element of the input data.
  • Rotating hash: Accumulate the ASCII code of each character into a hash value, performing a rotation operation on the hash value before each accumulation.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby simple_hash.py
def add_hash(key: str) -> int:\n    \"\"\"Additive hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash += ord(c)\n    return hash % modulus\n\ndef mul_hash(key: str) -> int:\n    \"\"\"Multiplicative hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash = 31 * hash + ord(c)\n    return hash % modulus\n\ndef xor_hash(key: str) -> int:\n    \"\"\"XOR hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash ^= ord(c)\n    return hash % modulus\n\ndef rot_hash(key: str) -> int:\n    \"\"\"Rotational hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash = (hash << 4) ^ (hash >> 28) ^ ord(c)\n    return hash % modulus\n
simple_hash.cpp
/* Additive hash */\nint addHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = (hash + (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint mulHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = (31 * hash + (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint xorHash(string key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash ^= (int)c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotational hash */\nint rotHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n
simple_hash.java
/* Additive hash */\nint addHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = (hash + (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n\n/* Multiplicative hash */\nint mulHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = (31 * hash + (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n\n/* XOR hash */\nint xorHash(String key) {\n    int hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash ^= (int) c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotational hash */\nint rotHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n
simple_hash.cs
/* Additive hash */\nint AddHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = (hash + c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint MulHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = (31 * hash + c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint XorHash(string key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash ^= c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotational hash */\nint RotHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS;\n    }\n    return (int)hash;\n}\n
simple_hash.go
/* Additive hash */\nfunc addHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = (hash + int64(b)) % modulus\n    }\n    return int(hash)\n}\n\n/* Multiplicative hash */\nfunc mulHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = (31*hash + int64(b)) % modulus\n    }\n    return int(hash)\n}\n\n/* XOR hash */\nfunc xorHash(key string) int {\n    hash := 0\n    modulus := 1000000007\n    for _, b := range []byte(key) {\n        fmt.Println(int(b))\n        hash ^= int(b)\n        hash = (31*hash + int(b)) % modulus\n    }\n    return hash & modulus\n}\n\n/* Rotational hash */\nfunc rotHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus\n    }\n    return int(hash)\n}\n
simple_hash.swift
/* Additive hash */\nfunc addHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = (hash + Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n\n/* Multiplicative hash */\nfunc mulHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = (31 * hash + Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n\n/* XOR hash */\nfunc xorHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash ^= Int(scalar.value)\n        }\n    }\n    return hash & MODULUS\n}\n\n/* Rotational hash */\nfunc rotHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n
simple_hash.js
/* Additive hash */\nfunction addHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* Multiplicative hash */\nfunction mulHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (31 * hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* XOR hash */\nfunction xorHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash ^= c.charCodeAt(0);\n    }\n    return hash % MODULUS;\n}\n\n/* Rotational hash */\nfunction rotHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n
simple_hash.ts
/* Additive hash */\nfunction addHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* Multiplicative hash */\nfunction mulHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (31 * hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* XOR hash */\nfunction xorHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash ^= c.charCodeAt(0);\n    }\n    return hash % MODULUS;\n}\n\n/* Rotational hash */\nfunction rotHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n
simple_hash.dart
/* Additive hash */\nint addHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = (hash + key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n\n/* Multiplicative hash */\nint mulHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = (31 * hash + key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n\n/* XOR hash */\nint xorHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash ^= key.codeUnitAt(i);\n  }\n  return hash & MODULUS;\n}\n\n/* Rotational hash */\nint rotHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n
simple_hash.rs
/* Additive hash */\nfn add_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = (hash + c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n\n/* Multiplicative hash */\nfn mul_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = (31 * hash + c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n\n/* XOR hash */\nfn xor_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash ^= c as i64;\n    }\n\n    (hash & MODULUS) as i32\n}\n\n/* Rotational hash */\nfn rot_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n
simple_hash.c
/* Additive hash */\nint addHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = (hash + (unsigned char)key[i]) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint mulHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = (31 * hash + (unsigned char)key[i]) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint xorHash(char *key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n\n    for (int i = 0; i < strlen(key); i++) {\n        hash ^= (unsigned char)key[i];\n    }\n    return hash & MODULUS;\n}\n\n/* Rotational hash */\nint rotHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS;\n    }\n\n    return (int)hash;\n}\n
simple_hash.kt
/* Additive hash */\nfun addHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = (hash + c.code) % MODULUS\n    }\n    return hash.toInt()\n}\n\n/* Multiplicative hash */\nfun mulHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = (31 * hash + c.code) % MODULUS\n    }\n    return hash.toInt()\n}\n\n/* XOR hash */\nfun xorHash(key: String): Int {\n    var hash = 0\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = hash xor c.code\n    }\n    return hash and MODULUS\n}\n\n/* Rotational hash */\nfun rotHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS\n    }\n    return hash.toInt()\n}\n
simple_hash.rb
### Additive hash ###\ndef add_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash += c.ord }\n\n  hash % modulus\nend\n\n### Multiplicative hash ###\ndef mul_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash = 31 * hash + c.ord }\n\n  hash % modulus\nend\n\n### XOR hash ###\ndef xor_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash ^= c.ord }\n\n  hash % modulus\nend\n\n### Rotational hash ###\ndef rot_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord }\n\n  hash % modulus\nend\n

It is observed that the last step of each hash algorithm is to take the modulus of the large prime number \\(1000000007\\) to ensure that the hash value is within an appropriate range. It is worth pondering why emphasis is placed on modulo a prime number, or what are the disadvantages of modulo a composite number? This is an interesting question.

To conclude: Using a large prime number as the modulus can maximize the uniform distribution of hash values. Since a prime number does not share common factors with other numbers, it can reduce the periodic patterns caused by the modulo operation, thus avoiding hash collisions.

For example, suppose we choose the composite number \\(9\\) as the modulus, which can be divided by \\(3\\), then all key divisible by \\(3\\) will be mapped to hash values \\(0\\), \\(3\\), \\(6\\).

\\[ \\begin{aligned} \\text{modulus} & = 9 \\newline \\text{key} & = \\{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \\dots \\} \\newline \\text{hash} & = \\{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\\dots \\} \\end{aligned} \\]

If the input key happens to have this kind of arithmetic sequence distribution, then the hash values will cluster, thereby exacerbating hash collisions. Now, suppose we replace modulus with the prime number \\(13\\), since there are no common factors between key and modulus, the uniformity of the output hash values will be significantly improved.

\\[ \\begin{aligned} \\text{modulus} & = 13 \\newline \\text{key} & = \\{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \\dots \\} \\newline \\text{hash} & = \\{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \\dots \\} \\end{aligned} \\]

It is worth noting that if the key is guaranteed to be randomly and uniformly distributed, then choosing a prime number or a composite number as the modulus can both produce uniformly distributed hash values. However, when the distribution of key has some periodicity, modulo a composite number is more likely to result in clustering.

In summary, we usually choose a prime number as the modulus, and this prime number should be large enough to eliminate periodic patterns as much as possible, enhancing the robustness of the hash algorithm.

","path":["Chapter 6. Hashing","6.3   Hash Algorithm"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#633-common-hash-algorithms","level":2,"title":"6.3.3   Common Hash Algorithms","text":"

It is not hard to see that the simple hash algorithms mentioned above are quite \"fragile\" and far from reaching the design goals of hash algorithms. For example, since addition and XOR obey the commutative law, additive hash and XOR hash cannot distinguish strings with the same content but in different order, which may exacerbate hash collisions and cause security issues.

In practice, we usually use some standard hash algorithms, such as MD5, SHA-1, SHA-2, and SHA-3. They can map input data of any length to a fixed-length hash value.

Over the past century, hash algorithms have been in a continuous process of upgrading and optimization. Some researchers strive to improve the performance of hash algorithms, while others, including hackers, are dedicated to finding security issues in hash algorithms. Table 6-2 shows hash algorithms commonly used in practical applications.

  • MD5 and SHA-1 have been successfully attacked multiple times and are thus abandoned in various security applications.
  • SHA-2 series, especially SHA-256, is one of the most secure hash algorithms to date, with no successful attacks reported, hence commonly used in various security applications and protocols.
  • SHA-3 has lower implementation costs and higher computational efficiency compared to SHA-2, but its current usage coverage is not as extensive as the SHA-2 series.

Table 6-2   Common hash algorithms

MD5 SHA-1 SHA-2 SHA-3 Release Year 1992 1995 2002 2008 Output Length 128 bit 160 bit 256/512 bit 224/256/384/512 bit Hash Collisions Frequent Frequent Rare Rare Security Level Low, has been successfully attacked Low, has been successfully attacked High High Applications Abandoned, still used for data integrity checks Abandoned Cryptocurrency transaction verification, digital signatures, etc. Can be used to replace SHA-2","path":["Chapter 6. Hashing","6.3   Hash Algorithm"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#hash-values-in-data-structures","level":1,"title":"Hash Values in Data Structures","text":"

We know that the keys in a hash table can be of various data types such as integers, decimals, or strings. Programming languages usually provide built-in hash algorithms for these data types to calculate the bucket indices in the hash table. Taking Python as an example, we can use the hash() function to compute the hash values for various data types.

  • The hash values of integers and booleans are their own values.
  • The calculation of hash values for floating-point numbers and strings is more complex, and interested readers are encouraged to study this on their own.
  • The hash value of a tuple is a combination of the hash values of each of its elements, resulting in a single hash value.
  • The hash value of an object is generated based on its memory address. By overriding the hash method of an object, hash values can be generated based on content.

Tip

Be aware that the definition and methods of the built-in hash value calculation functions in different programming languages vary.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby built_in_hash.py
num = 3\nhash_num = hash(num)\n# Hash value of integer 3 is 3\n\nbol = True\nhash_bol = hash(bol)\n# Hash value of boolean True is 1\n\ndec = 3.14159\nhash_dec = hash(dec)\n# Hash value of decimal 3.14159 is 326484311674566659\n\nstr = \"Hello 算法\"\nhash_str = hash(str)\n# Hash value of string \"Hello 算法\" is 4617003410720528961\n\ntup = (12836, \"小哈\")\nhash_tup = hash(tup)\n# Hash value of tuple (12836, '小哈') is 1029005403108185979\n\nobj = ListNode(0)\nhash_obj = hash(obj)\n# Hash value of ListNode object at 0x1058fd810 is 274267521\n
built_in_hash.cpp
int num = 3;\nsize_t hashNum = hash<int>()(num);\n// Hash value of integer 3 is 3\n\nbool bol = true;\nsize_t hashBol = hash<bool>()(bol);\n// Hash value of boolean 1 is 1\n\ndouble dec = 3.14159;\nsize_t hashDec = hash<double>()(dec);\n// Hash value of decimal 3.14159 is 4614256650576692846\n\nstring str = \"Hello 算法\";\nsize_t hashStr = hash<string>()(str);\n// Hash value of string \"Hello 算法\" is 15466937326284535026\n\n// In C++, built-in std::hash() only provides hash values for basic data types\n// Hash values for arrays and objects need to be implemented separately\n
built_in_hash.java
int num = 3;\nint hashNum = Integer.hashCode(num);\n// Hash value of integer 3 is 3\n\nboolean bol = true;\nint hashBol = Boolean.hashCode(bol);\n// Hash value of boolean true is 1231\n\ndouble dec = 3.14159;\nint hashDec = Double.hashCode(dec);\n// Hash value of decimal 3.14159 is -1340954729\n\nString str = \"Hello 算法\";\nint hashStr = str.hashCode();\n// Hash value of string \"Hello 算法\" is -727081396\n\nObject[] arr = { 12836, \"小哈\" };\nint hashTup = Arrays.hashCode(arr);\n// Hash value of array [12836, 小哈] is 1151158\n\nListNode obj = new ListNode(0);\nint hashObj = obj.hashCode();\n// Hash value of ListNode object utils.ListNode@7dc5e7b4 is 2110121908\n
built_in_hash.cs
int num = 3;\nint hashNum = num.GetHashCode();\n// Hash value of integer 3 is 3;\n\nbool bol = true;\nint hashBol = bol.GetHashCode();\n// Hash value of boolean true is 1;\n\ndouble dec = 3.14159;\nint hashDec = dec.GetHashCode();\n// Hash value of decimal 3.14159 is -1340954729;\n\nstring str = \"Hello 算法\";\nint hashStr = str.GetHashCode();\n// Hash value of string \"Hello 算法\" is -586107568;\n\nobject[] arr = [12836, \"小哈\"];\nint hashTup = arr.GetHashCode();\n// Hash value of array [12836, 小哈] is 42931033;\n\nListNode obj = new(0);\nint hashObj = obj.GetHashCode();\n// Hash value of ListNode object 0 is 39053774;\n
built_in_hash.go
// Go does not provide built-in hash code functions\n
built_in_hash.swift
let num = 3\nlet hashNum = num.hashValue\n// Hash value of integer 3 is 9047044699613009734\n\nlet bol = true\nlet hashBol = bol.hashValue\n// Hash value of boolean true is -4431640247352757451\n\nlet dec = 3.14159\nlet hashDec = dec.hashValue\n// Hash value of decimal 3.14159 is -2465384235396674631\n\nlet str = \"Hello 算法\"\nlet hashStr = str.hashValue\n// Hash value of string \"Hello 算法\" is -7850626797806988787\n\nlet arr = [AnyHashable(12836), AnyHashable(\"小哈\")]\nlet hashTup = arr.hashValue\n// Hash value of array [AnyHashable(12836), AnyHashable(\"小哈\")] is -2308633508154532996\n\nlet obj = ListNode(x: 0)\nlet hashObj = obj.hashValue\n// Hash value of ListNode object utils.ListNode is -2434780518035996159\n
built_in_hash.js
// JavaScript does not provide built-in hash code functions\n
built_in_hash.ts
// TypeScript does not provide built-in hash code functions\n
built_in_hash.dart
int num = 3;\nint hashNum = num.hashCode;\n// Hash value of integer 3 is 34803\n\nbool bol = true;\nint hashBol = bol.hashCode;\n// Hash value of boolean true is 1231\n\ndouble dec = 3.14159;\nint hashDec = dec.hashCode;\n// Hash value of decimal 3.14159 is 2570631074981783\n\nString str = \"Hello 算法\";\nint hashStr = str.hashCode;\n// Hash value of string \"Hello 算法\" is 468167534\n\nList arr = [12836, \"小哈\"];\nint hashArr = arr.hashCode;\n// Hash value of array [12836, 小哈] is 976512528\n\nListNode obj = new ListNode(0);\nint hashObj = obj.hashCode;\n// Hash value of ListNode object Instance of 'ListNode' is 1033450432\n
built_in_hash.rs
use std::collections::hash_map::DefaultHasher;\nuse std::hash::{Hash, Hasher};\n\nlet num = 3;\nlet mut num_hasher = DefaultHasher::new();\nnum.hash(&mut num_hasher);\nlet hash_num = num_hasher.finish();\n// Hash value of integer 3 is 568126464209439262\n\nlet bol = true;\nlet mut bol_hasher = DefaultHasher::new();\nbol.hash(&mut bol_hasher);\nlet hash_bol = bol_hasher.finish();\n// Hash value of boolean true is 4952851536318644461\n\nlet dec: f32 = 3.14159;\nlet mut dec_hasher = DefaultHasher::new();\ndec.to_bits().hash(&mut dec_hasher);\nlet hash_dec = dec_hasher.finish();\n// Hash value of decimal 3.14159 is 2566941990314602357\n\nlet str = \"Hello 算法\";\nlet mut str_hasher = DefaultHasher::new();\nstr.hash(&mut str_hasher);\nlet hash_str = str_hasher.finish();\n// Hash value of string \"Hello 算法\" is 16092673739211250988\n\nlet arr = (&12836, &\"小哈\");\nlet mut tup_hasher = DefaultHasher::new();\narr.hash(&mut tup_hasher);\nlet hash_tup = tup_hasher.finish();\n// Hash value of tuple (12836, \"小哈\") is 1885128010422702749\n\nlet node = ListNode::new(42);\nlet mut hasher = DefaultHasher::new();\nnode.borrow().val.hash(&mut hasher);\nlet hash = hasher.finish();\n// Hash value of ListNode object RefCell { value: ListNode { val: 42, next: None } } is 15387811073369036852\n
built_in_hash.c
// C does not provide built-in hash code functions\n
built_in_hash.kt
val num = 3\nval hashNum = num.hashCode()\n// Hash value of integer 3 is 3\n\nval bol = true\nval hashBol = bol.hashCode()\n// Hash value of boolean true is 1231\n\nval dec = 3.14159\nval hashDec = dec.hashCode()\n// Hash value of decimal 3.14159 is -1340954729\n\nval str = \"Hello 算法\"\nval hashStr = str.hashCode()\n// Hash value of string \"Hello 算法\" is -727081396\n\nval arr = arrayOf<Any>(12836, \"小哈\")\nval hashTup = arr.hashCode()\n// Hash value of array [12836, 小哈] is 189568618\n\nval obj = ListNode(0)\nval hashObj = obj.hashCode()\n// Hash value of ListNode object utils.ListNode@1d81eb93 is 495053715\n
built_in_hash.rb
num = 3\nhash_num = num.hash\n# Hash value of integer 3 is -4385856518450339636\n\nbol = true\nhash_bol = bol.hash\n# Hash value of boolean true is -1617938112149317027\n\ndec = 3.14159\nhash_dec = dec.hash\n# Hash value of decimal 3.14159 is -1479186995943067893\n\nstr = \"Hello 算法\"\nhash_str = str.hash\n# Hash value of string \"Hello 算法\" is -4075943250025831763\n\ntup = [12836, '小哈']\nhash_tup = tup.hash\n# Hash value of tuple (12836, '小哈') is 1999544809202288822\n\nobj = ListNode.new(0)\nhash_obj = obj.hash\n# Hash value of ListNode object #<ListNode:0x000078133140ab70> is 4302940560806366381\n
Visualized Execution

https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

In many programming languages, only immutable objects can serve as the key in a hash table. If we use a list (dynamic array) as a key, when the contents of the list change, its hash value also changes, and we would no longer be able to find the original value in the hash table.

Although the member variables of a custom object (such as a linked list node) are mutable, it is hashable. This is because the hash value of an object is usually generated based on its memory address, and even if the contents of the object change, the memory address remains the same, so the hash value remains unchanged.

You might have noticed that the hash values output in different consoles are different. This is because the Python interpreter adds a random salt to the string hash function each time it starts up. This approach effectively prevents HashDoS attacks and enhances the security of the hash algorithm.

","path":["Chapter 6. Hashing","6.3   Hash Algorithm"],"tags":[]},{"location":"chapter_hashing/hash_collision/","level":1,"title":"6.2   Hash Collision","text":"

The previous section mentioned that, in most cases, the input space of a hash function is much larger than the output space, so theoretically, hash collisions are inevitable. For example, if the input space is all integers and the output space is the array capacity size, then multiple integers will inevitably be mapped to the same bucket index.

Hash collisions can lead to incorrect query results, severely impacting the usability of the hash table. To address this issue, whenever a hash collision occurs, we can perform hash table expansion until the collision disappears. This approach is simple, straightforward, and effective, but it is very inefficient because hash table expansion involves a large amount of data migration and hash value recalculation. To improve efficiency, we can adopt the following strategies:

  1. Improve the hash table data structure so that the hash table can function normally when hash collisions occur.
  2. Only expand when necessary, that is, only when hash collisions are severe.

The main methods for improving the structure of hash tables include \"separate chaining\" and \"open addressing\".

","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#621-separate-chaining","level":2,"title":"6.2.1   Separate Chaining","text":"

In the original hash table, each bucket can store only one key-value pair. Separate chaining converts a single element into a linked list, treating key-value pairs as linked list nodes and storing all colliding key-value pairs in the same linked list. Figure 6-5 shows an example of a separate chaining hash table.

Figure 6-5   Separate chaining hash table

The operations of a hash table implemented with separate chaining have changed as follows:

  • Querying elements: Input key, obtain the bucket index through the hash function, then access the head node of the linked list, then traverse the linked list and compare key to find the target key-value pair.
  • Adding elements: First access the linked list head node through the hash function, then append the node (key-value pair) to the linked list.
  • Deleting elements: Access the head of the linked list based on the result of the hash function, then traverse the linked list to find the target node and delete it.

Separate chaining has the following limitations:

  • Increased Space Usage: The linked list contains node pointers, which consume more memory space than arrays.
  • Reduced Query Efficiency: This is because linear traversal of the linked list is required to find the corresponding element.

The code below provides a simple implementation of a separate chaining hash table, with two things to note:

  • Lists (dynamic arrays) are used instead of linked lists to simplify the code. In this setup, the hash table (array) contains multiple buckets, each of which is a list.
  • This implementation includes a hash table expansion method. When the load factor exceeds \\(\\frac{2}{3}\\), we expand the hash table to \\(2\\) times its original size.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map_chaining.py
class HashMapChaining:\n    \"\"\"Hash table with separate chaining\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self.size = 0  # Number of key-value pairs\n        self.capacity = 4  # Hash table capacity\n        self.load_thres = 2.0 / 3.0  # Load factor threshold for triggering expansion\n        self.extend_ratio = 2  # Expansion multiplier\n        self.buckets = [[] for _ in range(self.capacity)]  # Bucket array\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        return key % self.capacity\n\n    def load_factor(self) -> float:\n        \"\"\"Load factor\"\"\"\n        return self.size / self.capacity\n\n    def get(self, key: int) -> str | None:\n        \"\"\"Query operation\"\"\"\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse bucket, if key is found, return corresponding val\n        for pair in bucket:\n            if pair.key == key:\n                return pair.val\n        # If key is not found, return None\n        return None\n\n    def put(self, key: int, val: str):\n        \"\"\"Add operation\"\"\"\n        # When load factor exceeds threshold, perform expansion\n        if self.load_factor() > self.load_thres:\n            self.extend()\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse bucket, if specified key is encountered, update corresponding val and return\n        for pair in bucket:\n            if pair.key == key:\n                pair.val = val\n                return\n        # If key does not exist, append key-value pair to the end\n        pair = Pair(key, val)\n        bucket.append(pair)\n        self.size += 1\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse bucket and remove key-value pair from it\n        for pair in bucket:\n            if pair.key == key:\n                bucket.remove(pair)\n                self.size -= 1\n                break\n\n    def extend(self):\n        \"\"\"Expand hash table\"\"\"\n        # Temporarily store the original hash table\n        buckets = self.buckets\n        # Initialize expanded new hash table\n        self.capacity *= self.extend_ratio\n        self.buckets = [[] for _ in range(self.capacity)]\n        self.size = 0\n        # Move key-value pairs from original hash table to new hash table\n        for bucket in buckets:\n            for pair in bucket:\n                self.put(pair.key, pair.val)\n\n    def print(self):\n        \"\"\"Print hash table\"\"\"\n        for bucket in self.buckets:\n            res = []\n            for pair in bucket:\n                res.append(str(pair.key) + \" -> \" + pair.val)\n            print(res)\n
hash_map_chaining.cpp
/* Hash table with separate chaining */\nclass HashMapChaining {\n  private:\n    int size;                       // Number of key-value pairs\n    int capacity;                   // Hash table capacity\n    double loadThres;               // Load factor threshold for triggering expansion\n    int extendRatio;                // Expansion multiplier\n    vector<vector<Pair *>> buckets; // Bucket array\n\n  public:\n    /* Constructor */\n    HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) {\n        buckets.resize(capacity);\n    }\n\n    /* Destructor */\n    ~HashMapChaining() {\n        for (auto &bucket : buckets) {\n            for (Pair *pair : bucket) {\n                // Free memory\n                delete pair;\n            }\n        }\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double)size / (double)capacity;\n    }\n\n    /* Query operation */\n    string get(int key) {\n        int index = hashFunc(key);\n        // Traverse bucket, if key is found, return corresponding val\n        for (Pair *pair : buckets[index]) {\n            if (pair->key == key) {\n                return pair->val;\n            }\n        }\n        // Return empty string if key not found\n        return \"\";\n    }\n\n    /* Add operation */\n    void put(int key, string val) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        int index = hashFunc(key);\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for (Pair *pair : buckets[index]) {\n            if (pair->key == key) {\n                pair->val = val;\n                return;\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        buckets[index].push_back(new Pair(key, val));\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        auto &bucket = buckets[index];\n        // Traverse bucket and remove key-value pair from it\n        for (int i = 0; i < bucket.size(); i++) {\n            if (bucket[i]->key == key) {\n                Pair *tmp = bucket[i];\n                bucket.erase(bucket.begin() + i); // Remove key-value pair from it\n                delete tmp;                       // Free memory\n                size--;\n                return;\n            }\n        }\n    }\n\n    /* Expand hash table */\n    void extend() {\n        // Temporarily store the original hash table\n        vector<vector<Pair *>> bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets.clear();\n        buckets.resize(capacity);\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (auto &bucket : bucketsTmp) {\n            for (Pair *pair : bucket) {\n                put(pair->key, pair->val);\n                // Free memory\n                delete pair;\n            }\n        }\n    }\n\n    /* Print hash table */\n    void print() {\n        for (auto &bucket : buckets) {\n            cout << \"[\";\n            for (Pair *pair : bucket) {\n                cout << pair->key << \" -> \" << pair->val << \", \";\n            }\n            cout << \"]\\n\";\n        }\n    }\n};\n
hash_map_chaining.java
/* Hash table with separate chaining */\nclass HashMapChaining {\n    int size; // Number of key-value pairs\n    int capacity; // Hash table capacity\n    double loadThres; // Load factor threshold for triggering expansion\n    int extendRatio; // Expansion multiplier\n    List<List<Pair>> buckets; // Bucket array\n\n    /* Constructor */\n    public HashMapChaining() {\n        size = 0;\n        capacity = 4;\n        loadThres = 2.0 / 3.0;\n        extendRatio = 2;\n        buckets = new ArrayList<>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.add(new ArrayList<>());\n        }\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double) size / capacity;\n    }\n\n    /* Query operation */\n    String get(int key) {\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse bucket, if key is found, return corresponding val\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                return pair.val;\n            }\n        }\n        // If key is not found, return null\n        return null;\n    }\n\n    /* Add operation */\n    void put(int key, String val) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        Pair pair = new Pair(key, val);\n        bucket.add(pair);\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse bucket and remove key-value pair from it\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                bucket.remove(pair);\n                size--;\n                break;\n            }\n        }\n    }\n\n    /* Expand hash table */\n    void extend() {\n        // Temporarily store the original hash table\n        List<List<Pair>> bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets = new ArrayList<>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.add(new ArrayList<>());\n        }\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (List<Pair> bucket : bucketsTmp) {\n            for (Pair pair : bucket) {\n                put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    void print() {\n        for (List<Pair> bucket : buckets) {\n            List<String> res = new ArrayList<>();\n            for (Pair pair : bucket) {\n                res.add(pair.key + \" -> \" + pair.val);\n            }\n            System.out.println(res);\n        }\n    }\n}\n
hash_map_chaining.cs
/* Hash table with separate chaining */\nclass HashMapChaining {\n    int size; // Number of key-value pairs\n    int capacity; // Hash table capacity\n    double loadThres; // Load factor threshold for triggering expansion\n    int extendRatio; // Expansion multiplier\n    List<List<Pair>> buckets; // Bucket array\n\n    /* Constructor */\n    public HashMapChaining() {\n        size = 0;\n        capacity = 4;\n        loadThres = 2.0 / 3.0;\n        extendRatio = 2;\n        buckets = new List<List<Pair>>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.Add([]);\n        }\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double LoadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Query operation */\n    public string? Get(int key) {\n        int index = HashFunc(key);\n        // Traverse bucket, if key is found, return corresponding val\n        foreach (Pair pair in buckets[index]) {\n            if (pair.key == key) {\n                return pair.val;\n            }\n        }\n        // If key is not found, return null\n        return null;\n    }\n\n    /* Add operation */\n    public void Put(int key, string val) {\n        // When load factor exceeds threshold, perform expansion\n        if (LoadFactor() > loadThres) {\n            Extend();\n        }\n        int index = HashFunc(key);\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        foreach (Pair pair in buckets[index]) {\n            if (pair.key == key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        buckets[index].Add(new Pair(key, val));\n        size++;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        int index = HashFunc(key);\n        // Traverse bucket and remove key-value pair from it\n        foreach (Pair pair in buckets[index].ToList()) {\n            if (pair.key == key) {\n                buckets[index].Remove(pair);\n                size--;\n                break;\n            }\n        }\n    }\n\n    /* Expand hash table */\n    void Extend() {\n        // Temporarily store the original hash table\n        List<List<Pair>> bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets = new List<List<Pair>>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.Add([]);\n        }\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        foreach (List<Pair> bucket in bucketsTmp) {\n            foreach (Pair pair in bucket) {\n                Put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    public void Print() {\n        foreach (List<Pair> bucket in buckets) {\n            List<string> res = [];\n            foreach (Pair pair in bucket) {\n                res.Add(pair.key + \" -> \" + pair.val);\n            }\n            foreach (string kv in res) {\n                Console.WriteLine(kv);\n            }\n        }\n    }\n}\n
hash_map_chaining.go
/* Hash table with separate chaining */\ntype hashMapChaining struct {\n    size        int      // Number of key-value pairs\n    capacity    int      // Hash table capacity\n    loadThres   float64  // Load factor threshold for triggering expansion\n    extendRatio int      // Expansion multiplier\n    buckets     [][]pair // Bucket array\n}\n\n/* Constructor */\nfunc newHashMapChaining() *hashMapChaining {\n    buckets := make([][]pair, 4)\n    for i := 0; i < 4; i++ {\n        buckets[i] = make([]pair, 0)\n    }\n    return &hashMapChaining{\n        size:        0,\n        capacity:    4,\n        loadThres:   2.0 / 3.0,\n        extendRatio: 2,\n        buckets:     buckets,\n    }\n}\n\n/* Hash function */\nfunc (m *hashMapChaining) hashFunc(key int) int {\n    return key % m.capacity\n}\n\n/* Load factor */\nfunc (m *hashMapChaining) loadFactor() float64 {\n    return float64(m.size) / float64(m.capacity)\n}\n\n/* Query operation */\nfunc (m *hashMapChaining) get(key int) string {\n    idx := m.hashFunc(key)\n    bucket := m.buckets[idx]\n    // Traverse bucket, if key is found, return corresponding val\n    for _, p := range bucket {\n        if p.key == key {\n            return p.val\n        }\n    }\n    // Return empty string if key not found\n    return \"\"\n}\n\n/* Add operation */\nfunc (m *hashMapChaining) put(key int, val string) {\n    // When load factor exceeds threshold, perform expansion\n    if m.loadFactor() > m.loadThres {\n        m.extend()\n    }\n    idx := m.hashFunc(key)\n    // Traverse bucket, if specified key is encountered, update corresponding val and return\n    for i := range m.buckets[idx] {\n        if m.buckets[idx][i].key == key {\n            m.buckets[idx][i].val = val\n            return\n        }\n    }\n    // If key does not exist, append key-value pair to the end\n    p := pair{\n        key: key,\n        val: val,\n    }\n    m.buckets[idx] = append(m.buckets[idx], p)\n    m.size += 1\n}\n\n/* Remove operation */\nfunc (m *hashMapChaining) remove(key int) {\n    idx := m.hashFunc(key)\n    // Traverse bucket and remove key-value pair from it\n    for i, p := range m.buckets[idx] {\n        if p.key == key {\n            // Slice deletion\n            m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...)\n            m.size -= 1\n            break\n        }\n    }\n}\n\n/* Expand hash table */\nfunc (m *hashMapChaining) extend() {\n    // Temporarily store the original hash table\n    tmpBuckets := make([][]pair, len(m.buckets))\n    for i := 0; i < len(m.buckets); i++ {\n        tmpBuckets[i] = make([]pair, len(m.buckets[i]))\n        copy(tmpBuckets[i], m.buckets[i])\n    }\n    // Initialize expanded new hash table\n    m.capacity *= m.extendRatio\n    m.buckets = make([][]pair, m.capacity)\n    for i := 0; i < m.capacity; i++ {\n        m.buckets[i] = make([]pair, 0)\n    }\n    m.size = 0\n    // Move key-value pairs from original hash table to new hash table\n    for _, bucket := range tmpBuckets {\n        for _, p := range bucket {\n            m.put(p.key, p.val)\n        }\n    }\n}\n\n/* Print hash table */\nfunc (m *hashMapChaining) print() {\n    var builder strings.Builder\n\n    for _, bucket := range m.buckets {\n        builder.WriteString(\"[\")\n        for _, p := range bucket {\n            builder.WriteString(strconv.Itoa(p.key) + \" -> \" + p.val + \" \")\n        }\n        builder.WriteString(\"]\")\n        fmt.Println(builder.String())\n        builder.Reset()\n    }\n}\n
hash_map_chaining.swift
/* Hash table with separate chaining */\nclass HashMapChaining {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    var loadThres: Double // Load factor threshold for triggering expansion\n    var extendRatio: Int // Expansion multiplier\n    var buckets: [[Pair]] // Bucket array\n\n    /* Constructor */\n    init() {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = Array(repeating: [], count: capacity)\n    }\n\n    /* Hash function */\n    func hashFunc(key: Int) -> Int {\n        key % capacity\n    }\n\n    /* Load factor */\n    func loadFactor() -> Double {\n        Double(size) / Double(capacity)\n    }\n\n    /* Query operation */\n    func get(key: Int) -> String? {\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse bucket, if key is found, return corresponding val\n        for pair in bucket {\n            if pair.key == key {\n                return pair.val\n            }\n        }\n        // Return nil if key not found\n        return nil\n    }\n\n    /* Add operation */\n    func put(key: Int, val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if loadFactor() > loadThres {\n            extend()\n        }\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for pair in bucket {\n            if pair.key == key {\n                pair.val = val\n                return\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        let pair = Pair(key: key, val: val)\n        buckets[index].append(pair)\n        size += 1\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse bucket and remove key-value pair from it\n        for (pairIndex, pair) in bucket.enumerated() {\n            if pair.key == key {\n                buckets[index].remove(at: pairIndex)\n                size -= 1\n                break\n            }\n        }\n    }\n\n    /* Expand hash table */\n    func extend() {\n        // Temporarily store the original hash table\n        let bucketsTmp = buckets\n        // Initialize expanded new hash table\n        capacity *= extendRatio\n        buckets = Array(repeating: [], count: capacity)\n        size = 0\n        // Move key-value pairs from original hash table to new hash table\n        for bucket in bucketsTmp {\n            for pair in bucket {\n                put(key: pair.key, val: pair.val)\n            }\n        }\n    }\n\n    /* Print hash table */\n    func print() {\n        for bucket in buckets {\n            let res = bucket.map { \"\\($0.key) -> \\($0.val)\" }\n            Swift.print(res)\n        }\n    }\n}\n
hash_map_chaining.js
/* Hash table with separate chaining */\nclass HashMapChaining {\n    #size; // Number of key-value pairs\n    #capacity; // Hash table capacity\n    #loadThres; // Load factor threshold for triggering expansion\n    #extendRatio; // Expansion multiplier\n    #buckets; // Bucket array\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0;\n        this.#capacity = 4;\n        this.#loadThres = 2.0 / 3.0;\n        this.#extendRatio = 2;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor() {\n        return this.#size / this.#capacity;\n    }\n\n    /* Query operation */\n    get(key) {\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse bucket, if key is found, return corresponding val\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                return pair.val;\n            }\n        }\n        // If key is not found, return null\n        return null;\n    }\n\n    /* Add operation */\n    put(key, val) {\n        // When load factor exceeds threshold, perform expansion\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        const pair = new Pair(key, val);\n        bucket.push(pair);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key) {\n        const index = this.#hashFunc(key);\n        let bucket = this.#buckets[index];\n        // Traverse bucket and remove key-value pair from it\n        for (let i = 0; i < bucket.length; i++) {\n            if (bucket[i].key === key) {\n                bucket.splice(i, 1);\n                this.#size--;\n                break;\n            }\n        }\n    }\n\n    /* Expand hash table */\n    #extend() {\n        // Temporarily store the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize expanded new hash table\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n        this.#size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (const bucket of bucketsTmp) {\n            for (const pair of bucket) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    print() {\n        for (const bucket of this.#buckets) {\n            let res = [];\n            for (const pair of bucket) {\n                res.push(pair.key + ' -> ' + pair.val);\n            }\n            console.log(res);\n        }\n    }\n}\n
hash_map_chaining.ts
/* Hash table with separate chaining */\nclass HashMapChaining {\n    #size: number; // Number of key-value pairs\n    #capacity: number; // Hash table capacity\n    #loadThres: number; // Load factor threshold for triggering expansion\n    #extendRatio: number; // Expansion multiplier\n    #buckets: Pair[][]; // Bucket array\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0;\n        this.#capacity = 4;\n        this.#loadThres = 2.0 / 3.0;\n        this.#extendRatio = 2;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n    }\n\n    /* Hash function */\n    #hashFunc(key: number): number {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor(): number {\n        return this.#size / this.#capacity;\n    }\n\n    /* Query operation */\n    get(key: number): string | null {\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse bucket, if key is found, return corresponding val\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                return pair.val;\n            }\n        }\n        // If key is not found, return null\n        return null;\n    }\n\n    /* Add operation */\n    put(key: number, val: string): void {\n        // When load factor exceeds threshold, perform expansion\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        const pair = new Pair(key, val);\n        bucket.push(pair);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key: number): void {\n        const index = this.#hashFunc(key);\n        let bucket = this.#buckets[index];\n        // Traverse bucket and remove key-value pair from it\n        for (let i = 0; i < bucket.length; i++) {\n            if (bucket[i].key === key) {\n                bucket.splice(i, 1);\n                this.#size--;\n                break;\n            }\n        }\n    }\n\n    /* Expand hash table */\n    #extend(): void {\n        // Temporarily store the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize expanded new hash table\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n        this.#size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (const bucket of bucketsTmp) {\n            for (const pair of bucket) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    print(): void {\n        for (const bucket of this.#buckets) {\n            let res = [];\n            for (const pair of bucket) {\n                res.push(pair.key + ' -> ' + pair.val);\n            }\n            console.log(res);\n        }\n    }\n}\n
hash_map_chaining.dart
/* Hash table with separate chaining */\nclass HashMapChaining {\n  late int size; // Number of key-value pairs\n  late int capacity; // Hash table capacity\n  late double loadThres; // Load factor threshold for triggering expansion\n  late int extendRatio; // Expansion multiplier\n  late List<List<Pair>> buckets; // Bucket array\n\n  /* Constructor */\n  HashMapChaining() {\n    size = 0;\n    capacity = 4;\n    loadThres = 2.0 / 3.0;\n    extendRatio = 2;\n    buckets = List.generate(capacity, (_) => []);\n  }\n\n  /* Hash function */\n  int hashFunc(int key) {\n    return key % capacity;\n  }\n\n  /* Load factor */\n  double loadFactor() {\n    return size / capacity;\n  }\n\n  /* Query operation */\n  String? get(int key) {\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse bucket, if key is found, return corresponding val\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        return pair.val;\n      }\n    }\n    // If key is not found, return null\n    return null;\n  }\n\n  /* Add operation */\n  void put(int key, String val) {\n    // When load factor exceeds threshold, perform expansion\n    if (loadFactor() > loadThres) {\n      extend();\n    }\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse bucket, if specified key is encountered, update corresponding val and return\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        pair.val = val;\n        return;\n      }\n    }\n    // If key does not exist, append key-value pair to the end\n    Pair pair = Pair(key, val);\n    bucket.add(pair);\n    size++;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse bucket and remove key-value pair from it\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        bucket.remove(pair);\n        size--;\n        break;\n      }\n    }\n  }\n\n  /* Expand hash table */\n  void extend() {\n    // Temporarily store the original hash table\n    List<List<Pair>> bucketsTmp = buckets;\n    // Initialize expanded new hash table\n    capacity *= extendRatio;\n    buckets = List.generate(capacity, (_) => []);\n    size = 0;\n    // Move key-value pairs from original hash table to new hash table\n    for (List<Pair> bucket in bucketsTmp) {\n      for (Pair pair in bucket) {\n        put(pair.key, pair.val);\n      }\n    }\n  }\n\n  /* Print hash table */\n  void printHashMap() {\n    for (List<Pair> bucket in buckets) {\n      List<String> res = [];\n      for (Pair pair in bucket) {\n        res.add(\"${pair.key} -> ${pair.val}\");\n      }\n      print(res);\n    }\n  }\n}\n
hash_map_chaining.rs
/* Hash table with separate chaining */\nstruct HashMapChaining {\n    size: usize,\n    capacity: usize,\n    load_thres: f32,\n    extend_ratio: usize,\n    buckets: Vec<Vec<Pair>>,\n}\n\nimpl HashMapChaining {\n    /* Constructor */\n    fn new() -> Self {\n        Self {\n            size: 0,\n            capacity: 4,\n            load_thres: 2.0 / 3.0,\n            extend_ratio: 2,\n            buckets: vec![vec![]; 4],\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        key as usize % self.capacity\n    }\n\n    /* Load factor */\n    fn load_factor(&self) -> f32 {\n        self.size as f32 / self.capacity as f32\n    }\n\n    /* Remove operation */\n    fn remove(&mut self, key: i32) -> Option<String> {\n        let index = self.hash_func(key);\n\n        // Traverse bucket and remove key-value pair from it\n        for (i, p) in self.buckets[index].iter_mut().enumerate() {\n            if p.key == key {\n                let pair = self.buckets[index].remove(i);\n                self.size -= 1;\n                return Some(pair.val);\n            }\n        }\n\n        // If key is not found, return None\n        None\n    }\n\n    /* Expand hash table */\n    fn extend(&mut self) {\n        // Temporarily store the original hash table\n        let buckets_tmp = std::mem::take(&mut self.buckets);\n\n        // Initialize expanded new hash table\n        self.capacity *= self.extend_ratio;\n        self.buckets = vec![Vec::new(); self.capacity as usize];\n        self.size = 0;\n\n        // Move key-value pairs from original hash table to new hash table\n        for bucket in buckets_tmp {\n            for pair in bucket {\n                self.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    fn print(&self) {\n        for bucket in &self.buckets {\n            let mut res = Vec::new();\n            for pair in bucket {\n                res.push(format!(\"{} -> {}\", pair.key, pair.val));\n            }\n            println!(\"{:?}\", res);\n        }\n    }\n\n    /* Add operation */\n    fn put(&mut self, key: i32, val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if self.load_factor() > self.load_thres {\n            self.extend();\n        }\n\n        let index = self.hash_func(key);\n\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for pair in self.buckets[index].iter_mut() {\n            if pair.key == key {\n                pair.val = val;\n                return;\n            }\n        }\n\n        // If key does not exist, append key-value pair to the end\n        let pair = Pair { key, val };\n        self.buckets[index].push(pair);\n        self.size += 1;\n    }\n\n    /* Query operation */\n    fn get(&self, key: i32) -> Option<&str> {\n        let index = self.hash_func(key);\n\n        // Traverse bucket, if key is found, return corresponding val\n        for pair in self.buckets[index].iter() {\n            if pair.key == key {\n                return Some(&pair.val);\n            }\n        }\n\n        // If key is not found, return None\n        None\n    }\n}\n
hash_map_chaining.c
/* Linked list node */\ntypedef struct Node {\n    Pair *pair;\n    struct Node *next;\n} Node;\n\n/* Hash table with separate chaining */\ntypedef struct {\n    int size;         // Number of key-value pairs\n    int capacity;     // Hash table capacity\n    double loadThres; // Load factor threshold for triggering expansion\n    int extendRatio;  // Expansion multiplier\n    Node **buckets;   // Bucket array\n} HashMapChaining;\n\n/* Constructor */\nHashMapChaining *newHashMapChaining() {\n    HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining));\n    hashMap->size = 0;\n    hashMap->capacity = 4;\n    hashMap->loadThres = 2.0 / 3.0;\n    hashMap->extendRatio = 2;\n    hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *));\n    for (int i = 0; i < hashMap->capacity; i++) {\n        hashMap->buckets[i] = NULL;\n    }\n    return hashMap;\n}\n\n/* Destructor */\nvoid delHashMapChaining(HashMapChaining *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Node *cur = hashMap->buckets[i];\n        while (cur) {\n            Node *tmp = cur;\n            cur = cur->next;\n            free(tmp->pair);\n            free(tmp);\n        }\n    }\n    free(hashMap->buckets);\n    free(hashMap);\n}\n\n/* Hash function */\nint hashFunc(HashMapChaining *hashMap, int key) {\n    return key % hashMap->capacity;\n}\n\n/* Load factor */\ndouble loadFactor(HashMapChaining *hashMap) {\n    return (double)hashMap->size / (double)hashMap->capacity;\n}\n\n/* Query operation */\nchar *get(HashMapChaining *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    // Traverse bucket, if key is found, return corresponding val\n    Node *cur = hashMap->buckets[index];\n    while (cur) {\n        if (cur->pair->key == key) {\n            return cur->pair->val;\n        }\n        cur = cur->next;\n    }\n    return \"\"; // Return empty string if key not found\n}\n\n/* Add operation */\nvoid put(HashMapChaining *hashMap, int key, const char *val) {\n    // When load factor exceeds threshold, perform expansion\n    if (loadFactor(hashMap) > hashMap->loadThres) {\n        extend(hashMap);\n    }\n    int index = hashFunc(hashMap, key);\n    // Traverse bucket, if specified key is encountered, update corresponding val and return\n    Node *cur = hashMap->buckets[index];\n    while (cur) {\n        if (cur->pair->key == key) {\n            strcpy(cur->pair->val, val); // If specified key is found, update corresponding val and return\n            return;\n        }\n        cur = cur->next;\n    }\n    // If key not found, add key-value pair to list head\n    Pair *newPair = (Pair *)malloc(sizeof(Pair));\n    newPair->key = key;\n    strcpy(newPair->val, val);\n    Node *newNode = (Node *)malloc(sizeof(Node));\n    newNode->pair = newPair;\n    newNode->next = hashMap->buckets[index];\n    hashMap->buckets[index] = newNode;\n    hashMap->size++;\n}\n\n/* Expand hash table */\nvoid extend(HashMapChaining *hashMap) {\n    // Temporarily store the original hash table\n    int oldCapacity = hashMap->capacity;\n    Node **oldBuckets = hashMap->buckets;\n    // Initialize expanded new hash table\n    hashMap->capacity *= hashMap->extendRatio;\n    hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *));\n    for (int i = 0; i < hashMap->capacity; i++) {\n        hashMap->buckets[i] = NULL;\n    }\n    hashMap->size = 0;\n    // Move key-value pairs from original hash table to new hash table\n    for (int i = 0; i < oldCapacity; i++) {\n        Node *cur = oldBuckets[i];\n        while (cur) {\n            put(hashMap, cur->pair->key, cur->pair->val);\n            Node *temp = cur;\n            cur = cur->next;\n            // Free memory\n            free(temp->pair);\n            free(temp);\n        }\n    }\n\n    free(oldBuckets);\n}\n\n/* Remove operation */\nvoid removeItem(HashMapChaining *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    Node *cur = hashMap->buckets[index];\n    Node *pre = NULL;\n    while (cur) {\n        if (cur->pair->key == key) {\n            // Remove key-value pair from it\n            if (pre) {\n                pre->next = cur->next;\n            } else {\n                hashMap->buckets[index] = cur->next;\n            }\n            // Free memory\n            free(cur->pair);\n            free(cur);\n            hashMap->size--;\n            return;\n        }\n        pre = cur;\n        cur = cur->next;\n    }\n}\n\n/* Print hash table */\nvoid print(HashMapChaining *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Node *cur = hashMap->buckets[i];\n        printf(\"[\");\n        while (cur) {\n            printf(\"%d -> %s, \", cur->pair->key, cur->pair->val);\n            cur = cur->next;\n        }\n        printf(\"]\\n\");\n    }\n}\n
hash_map_chaining.kt
/* Hash table with separate chaining */\nclass HashMapChaining {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    val loadThres: Double // Load factor threshold for triggering expansion\n    val extendRatio: Int // Expansion multiplier\n    var buckets: MutableList<MutableList<Pair>> // Bucket array\n\n    /* Constructor */\n    init {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = mutableListOf()\n        for (i in 0..<capacity) {\n            buckets.add(mutableListOf())\n        }\n    }\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        return key % capacity\n    }\n\n    /* Load factor */\n    fun loadFactor(): Double {\n        return (size / capacity).toDouble()\n    }\n\n    /* Query operation */\n    fun get(key: Int): String? {\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse bucket, if key is found, return corresponding val\n        for (pair in bucket) {\n            if (pair.key == key) return pair._val\n        }\n        // If key is not found, return null\n        return null\n    }\n\n    /* Add operation */\n    fun put(key: Int, _val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend()\n        }\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse bucket, if specified key is encountered, update corresponding val and return\n        for (pair in bucket) {\n            if (pair.key == key) {\n                pair._val = _val\n                return\n            }\n        }\n        // If key does not exist, append key-value pair to the end\n        val pair = Pair(key, _val)\n        bucket.add(pair)\n        size++\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse bucket and remove key-value pair from it\n        for (pair in bucket) {\n            if (pair.key == key) {\n                bucket.remove(pair)\n                size--\n                break\n            }\n        }\n    }\n\n    /* Expand hash table */\n    fun extend() {\n        // Temporarily store the original hash table\n        val bucketsTmp = buckets\n        // Initialize expanded new hash table\n        capacity *= extendRatio\n        // mutablelist has no fixed size\n        buckets = mutableListOf()\n        for (i in 0..<capacity) {\n            buckets.add(mutableListOf())\n        }\n        size = 0\n        // Move key-value pairs from original hash table to new hash table\n        for (bucket in bucketsTmp) {\n            for (pair in bucket) {\n                put(pair.key, pair._val)\n            }\n        }\n    }\n\n    /* Print hash table */\n    fun print() {\n        for (bucket in buckets) {\n            val res = mutableListOf<String>()\n            for (pair in bucket) {\n                val k = pair.key\n                val v = pair._val\n                res.add(\"$k -> $v\")\n            }\n            println(res)\n        }\n    }\n}\n
hash_map_chaining.rb
### Hash map with chaining ###\nclass HashMapChaining\n  ### Constructor ###\n  def initialize\n    @size = 0 # Number of key-value pairs\n    @capacity = 4 # Hash table capacity\n    @load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion\n    @extend_ratio = 2 # Expansion multiplier\n    @buckets = Array.new(@capacity) { [] } # Bucket array\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    key % @capacity\n  end\n\n  ### Load factor ###\n  def load_factor\n    @size / @capacity\n  end\n\n  ### Query operation ###\n  def get(key)\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse bucket, if key is found, return corresponding val\n    for pair in bucket\n      return pair.val if pair.key == key\n    end\n    # Return nil if key not found\n    nil\n  end\n\n  ### Add operation ###\n  def put(key, val)\n    # When load factor exceeds threshold, perform expansion\n    extend if load_factor > @load_thres\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse bucket, if specified key is encountered, update corresponding val and return\n    for pair in bucket\n      if pair.key == key\n        pair.val = val\n        return\n      end\n    end\n    # If key does not exist, append key-value pair to the end\n    pair = Pair.new(key, val)\n    bucket << pair\n    @size += 1\n  end\n\n  ### Delete operation ###\n  def remove(key)\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse bucket and remove key-value pair from it\n    for pair in bucket\n      if pair.key == key\n        bucket.delete(pair)\n        @size -= 1\n        break\n      end\n    end\n  end\n\n  ### Expand hash table ###\n  def extend\n    # Temporarily store original hash table\n    buckets = @buckets\n    # Initialize expanded new hash table\n    @capacity *= @extend_ratio\n    @buckets = Array.new(@capacity) { [] }\n    @size = 0\n    # Move key-value pairs from original hash table to new hash table\n    for bucket in buckets\n      for pair in bucket\n        put(pair.key, pair.val)\n      end\n    end\n  end\n\n  ### Print hash table ###\n  def print\n    for bucket in @buckets\n      res = []\n      for pair in bucket\n        res << \"#{pair.key} -> #{pair.val}\"\n      end\n      pp res\n    end\n  end\nend\n

It's worth noting that when the linked list is very long, the query efficiency \\(O(n)\\) is poor. In this case, the list can be converted to an \"AVL tree\" or \"Red-Black tree\" to optimize the time complexity of the query operation to \\(O(\\log n)\\).

","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#622-open-addressing","level":2,"title":"6.2.2   Open Addressing","text":"

Open addressing does not introduce additional data structures but instead handles hash collisions through \"multiple probes\". The probing methods mainly include linear probing, quadratic probing, and double hashing.

Let's use linear probing as an example to introduce the mechanism of open addressing hash tables.

","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#1-linear-probing","level":3,"title":"1.   Linear Probing","text":"

Linear probing uses a fixed-step linear search for probing, and its operation method differs from ordinary hash tables.

  • Inserting elements: Calculate the bucket index using the hash function. If the bucket already contains an element, linearly traverse forward from the conflict position (usually with a step size of \\(1\\)) until an empty bucket is found, then insert the element.
  • Searching for elements: If a hash collision is encountered, use the same step size to linearly traverse forward until the corresponding element is found and return value; if an empty bucket is encountered, it means the target element is not in the hash table, so return None.

Figure 6-6 shows the distribution of key-value pairs in an open addressing (linear probing) hash table. According to this hash function, keys with the same last two digits will be mapped to the same bucket. Through linear probing, they are stored sequentially in that bucket and the buckets below it.

Figure 6-6   Distribution of key-value pairs in open addressing (linear probing) hash table

However, linear probing is prone to create \"clustering\". Specifically, the longer the continuously occupied positions in the array, the greater the probability of hash collisions occurring in these continuous positions, further promoting clustering growth at that position, forming a vicious cycle, and ultimately leading to degraded efficiency of insertion, deletion, query, and update operations.

It's important to note that we cannot directly delete elements in an open addressing hash table. Deleting an element creates an empty bucket None in the array. When searching for elements, if linear probing encounters this empty bucket, it will return, making the elements below this empty bucket inaccessible. The program may incorrectly assume these elements do not exist, as shown in Figure 6-7.

Figure 6-7   Query issues caused by deletion in open addressing

To solve this problem, we can adopt the lazy deletion mechanism: instead of directly removing elements from the hash table, use a constant TOMBSTONE to mark the bucket. In this mechanism, both None and TOMBSTONE represent empty buckets and can hold key-value pairs. However, when linear probing encounters TOMBSTONE, it should continue traversing since there may still be key-value pairs below it.

However, lazy deletion may accelerate the performance degradation of the hash table. Every deletion operation produces a deletion mark, and as TOMBSTONE increases, the search time will also increase because linear probing may need to skip multiple TOMBSTONE to find the target element.

To address this, consider recording the index of the first encountered TOMBSTONE during linear probing and swapping the searched target element with that TOMBSTONE. The benefit of doing this is that each time an element is queried or added, the element will be moved to a bucket closer to its ideal position (the starting point of probing), thereby optimizing query efficiency.

The code below implements an open addressing (linear probing) hash table with lazy deletion. To make better use of the hash table space, we treat the hash table as a \"circular array\". When going beyond the end of the array, we return to the beginning and continue traversing.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map_open_addressing.py
class HashMapOpenAddressing:\n    \"\"\"Hash table with open addressing\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self.size = 0  # Number of key-value pairs\n        self.capacity = 4  # Hash table capacity\n        self.load_thres = 2.0 / 3.0  # Load factor threshold for triggering expansion\n        self.extend_ratio = 2  # Expansion multiplier\n        self.buckets: list[Pair | None] = [None] * self.capacity  # Bucket array\n        self.TOMBSTONE = Pair(-1, \"-1\")  # Removal marker\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        return key % self.capacity\n\n    def load_factor(self) -> float:\n        \"\"\"Load factor\"\"\"\n        return self.size / self.capacity\n\n    def find_bucket(self, key: int) -> int:\n        \"\"\"Search for bucket index corresponding to key\"\"\"\n        index = self.hash_func(key)\n        first_tombstone = -1\n        # Linear probing, break when encountering an empty bucket\n        while self.buckets[index] is not None:\n            # If key is encountered, return the corresponding bucket index\n            if self.buckets[index].key == key:\n                # If a removal marker was encountered before, move the key-value pair to that index\n                if first_tombstone != -1:\n                    self.buckets[first_tombstone] = self.buckets[index]\n                    self.buckets[index] = self.TOMBSTONE\n                    return first_tombstone  # Return the moved bucket index\n                return index  # Return bucket index\n            # Record the first removal marker encountered\n            if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE:\n                first_tombstone = index\n            # Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % self.capacity\n        # If key does not exist, return the index for insertion\n        return index if first_tombstone == -1 else first_tombstone\n\n    def get(self, key: int) -> str:\n        \"\"\"Query operation\"\"\"\n        # Search for bucket index corresponding to key\n        index = self.find_bucket(key)\n        # If key-value pair is found, return corresponding val\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            return self.buckets[index].val\n        # If key-value pair does not exist, return None\n        return None\n\n    def put(self, key: int, val: str):\n        \"\"\"Add operation\"\"\"\n        # When load factor exceeds threshold, perform expansion\n        if self.load_factor() > self.load_thres:\n            self.extend()\n        # Search for bucket index corresponding to key\n        index = self.find_bucket(key)\n        # If key-value pair is found, overwrite val and return\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            self.buckets[index].val = val\n            return\n        # If key-value pair does not exist, add the key-value pair\n        self.buckets[index] = Pair(key, val)\n        self.size += 1\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        # Search for bucket index corresponding to key\n        index = self.find_bucket(key)\n        # If key-value pair is found, overwrite it with removal marker\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            self.buckets[index] = self.TOMBSTONE\n            self.size -= 1\n\n    def extend(self):\n        \"\"\"Expand hash table\"\"\"\n        # Temporarily store the original hash table\n        buckets_tmp = self.buckets\n        # Initialize expanded new hash table\n        self.capacity *= self.extend_ratio\n        self.buckets = [None] * self.capacity\n        self.size = 0\n        # Move key-value pairs from original hash table to new hash table\n        for pair in buckets_tmp:\n            if pair not in [None, self.TOMBSTONE]:\n                self.put(pair.key, pair.val)\n\n    def print(self):\n        \"\"\"Print hash table\"\"\"\n        for pair in self.buckets:\n            if pair is None:\n                print(\"None\")\n            elif pair is self.TOMBSTONE:\n                print(\"TOMBSTONE\")\n            else:\n                print(pair.key, \"->\", pair.val)\n
hash_map_open_addressing.cpp
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n  private:\n    int size;                             // Number of key-value pairs\n    int capacity = 4;                     // Hash table capacity\n    const double loadThres = 2.0 / 3.0;     // Load factor threshold for triggering expansion\n    const int extendRatio = 2;            // Expansion multiplier\n    vector<Pair *> buckets;               // Bucket array\n    Pair *TOMBSTONE = new Pair(-1, \"-1\"); // Removal marker\n\n  public:\n    /* Constructor */\n    HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) {\n    }\n\n    /* Destructor */\n    ~HashMapOpenAddressing() {\n        for (Pair *pair : buckets) {\n            if (pair != nullptr && pair != TOMBSTONE) {\n                delete pair;\n            }\n        }\n        delete TOMBSTONE;\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Search for bucket index corresponding to key */\n    int findBucket(int key) {\n        int index = hashFunc(key);\n        int firstTombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while (buckets[index] != nullptr) {\n            // If key is encountered, return the corresponding bucket index\n            if (buckets[index]->key == key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % capacity;\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Query operation */\n    string get(int key) {\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, return corresponding val\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            return buckets[index]->val;\n        }\n        // Return empty string if key-value pair does not exist\n        return \"\";\n    }\n\n    /* Add operation */\n    void put(int key, string val) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, overwrite val and return\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            buckets[index]->val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            delete buckets[index];\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Expand hash table */\n    void extend() {\n        // Temporarily store the original hash table\n        vector<Pair *> bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets = vector<Pair *>(capacity, nullptr);\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (Pair *pair : bucketsTmp) {\n            if (pair != nullptr && pair != TOMBSTONE) {\n                put(pair->key, pair->val);\n                delete pair;\n            }\n        }\n    }\n\n    /* Print hash table */\n    void print() {\n        for (Pair *pair : buckets) {\n            if (pair == nullptr) {\n                cout << \"nullptr\" << endl;\n            } else if (pair == TOMBSTONE) {\n                cout << \"TOMBSTONE\" << endl;\n            } else {\n                cout << pair->key << \" -> \" << pair->val << endl;\n            }\n        }\n    }\n};\n
hash_map_open_addressing.java
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private int size; // Number of key-value pairs\n    private int capacity = 4; // Hash table capacity\n    private final double loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion\n    private final int extendRatio = 2; // Expansion multiplier\n    private Pair[] buckets; // Bucket array\n    private final Pair TOMBSTONE = new Pair(-1, \"-1\"); // Removal marker\n\n    /* Constructor */\n    public HashMapOpenAddressing() {\n        size = 0;\n        buckets = new Pair[capacity];\n    }\n\n    /* Hash function */\n    private int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    private double loadFactor() {\n        return (double) size / capacity;\n    }\n\n    /* Search for bucket index corresponding to key */\n    private int findBucket(int key) {\n        int index = hashFunc(key);\n        int firstTombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while (buckets[index] != null) {\n            // If key is encountered, return the corresponding bucket index\n            if (buckets[index].key == key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % capacity;\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Query operation */\n    public String get(int key) {\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, return corresponding val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index].val;\n        }\n        // If key-value pair does not exist, return null\n        return null;\n    }\n\n    /* Add operation */\n    public void put(int key, String val) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index].val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    public void remove(int key) {\n        // Search for bucket index corresponding to key\n        int index = findBucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Expand hash table */\n    private void extend() {\n        // Temporarily store the original hash table\n        Pair[] bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets = new Pair[capacity];\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (Pair pair : bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    public void print() {\n        for (Pair pair : buckets) {\n            if (pair == null) {\n                System.out.println(\"null\");\n            } else if (pair == TOMBSTONE) {\n                System.out.println(\"TOMBSTONE\");\n            } else {\n                System.out.println(pair.key + \" -> \" + pair.val);\n            }\n        }\n    }\n}\n
hash_map_open_addressing.cs
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    int size; // Number of key-value pairs\n    int capacity = 4; // Hash table capacity\n    double loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion\n    int extendRatio = 2; // Expansion multiplier\n    Pair[] buckets; // Bucket array\n    Pair TOMBSTONE = new(-1, \"-1\"); // Removal marker\n\n    /* Constructor */\n    public HashMapOpenAddressing() {\n        size = 0;\n        buckets = new Pair[capacity];\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double LoadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Search for bucket index corresponding to key */\n    int FindBucket(int key) {\n        int index = HashFunc(key);\n        int firstTombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while (buckets[index] != null) {\n            // If key is encountered, return the corresponding bucket index\n            if (buckets[index].key == key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % capacity;\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Query operation */\n    public string? Get(int key) {\n        // Search for bucket index corresponding to key\n        int index = FindBucket(key);\n        // If key-value pair is found, return corresponding val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index].val;\n        }\n        // If key-value pair does not exist, return null\n        return null;\n    }\n\n    /* Add operation */\n    public void Put(int key, string val) {\n        // When load factor exceeds threshold, perform expansion\n        if (LoadFactor() > loadThres) {\n            Extend();\n        }\n        // Search for bucket index corresponding to key\n        int index = FindBucket(key);\n        // If key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index].val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        // Search for bucket index corresponding to key\n        int index = FindBucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Expand hash table */\n    void Extend() {\n        // Temporarily store the original hash table\n        Pair[] bucketsTmp = buckets;\n        // Initialize expanded new hash table\n        capacity *= extendRatio;\n        buckets = new Pair[capacity];\n        size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        foreach (Pair pair in bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                Put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    public void Print() {\n        foreach (Pair pair in buckets) {\n            if (pair == null) {\n                Console.WriteLine(\"null\");\n            } else if (pair == TOMBSTONE) {\n                Console.WriteLine(\"TOMBSTONE\");\n            } else {\n                Console.WriteLine(pair.key + \" -> \" + pair.val);\n            }\n        }\n    }\n}\n
hash_map_open_addressing.go
/* Hash table with open addressing */\ntype hashMapOpenAddressing struct {\n    size        int     // Number of key-value pairs\n    capacity    int     // Hash table capacity\n    loadThres   float64 // Load factor threshold for triggering expansion\n    extendRatio int     // Expansion multiplier\n    buckets     []*pair // Bucket array\n    TOMBSTONE   *pair   // Removal marker\n}\n\n/* Constructor */\nfunc newHashMapOpenAddressing() *hashMapOpenAddressing {\n    return &hashMapOpenAddressing{\n        size:        0,\n        capacity:    4,\n        loadThres:   2.0 / 3.0,\n        extendRatio: 2,\n        buckets:     make([]*pair, 4),\n        TOMBSTONE:   &pair{-1, \"-1\"},\n    }\n}\n\n/* Hash function */\nfunc (h *hashMapOpenAddressing) hashFunc(key int) int {\n    return key % h.capacity // Calculate hash value based on key\n}\n\n/* Load factor */\nfunc (h *hashMapOpenAddressing) loadFactor() float64 {\n    return float64(h.size) / float64(h.capacity) // Calculate current load factor\n}\n\n/* Search for bucket index corresponding to key */\nfunc (h *hashMapOpenAddressing) findBucket(key int) int {\n    index := h.hashFunc(key) // Get initial index\n    firstTombstone := -1     // Record position of first TOMBSTONE encountered\n    for h.buckets[index] != nil {\n        if h.buckets[index].key == key {\n            if firstTombstone != -1 {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                h.buckets[firstTombstone] = h.buckets[index]\n                h.buckets[index] = h.TOMBSTONE\n                return firstTombstone // Return the moved bucket index\n            }\n            return index // Return found index\n        }\n        if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE {\n            firstTombstone = index // Record position of first deletion marker encountered\n        }\n        index = (index + 1) % h.capacity // Linear probing, wrap around to head if past tail\n    }\n    // If key does not exist, return the index for insertion\n    if firstTombstone != -1 {\n        return firstTombstone\n    }\n    return index\n}\n\n/* Query operation */\nfunc (h *hashMapOpenAddressing) get(key int) string {\n    index := h.findBucket(key) // Search for bucket index corresponding to key\n    if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE {\n        return h.buckets[index].val // If key-value pair is found, return corresponding val\n    }\n    return \"\" // Return \"\" if key-value pair does not exist\n}\n\n/* Add operation */\nfunc (h *hashMapOpenAddressing) put(key int, val string) {\n    if h.loadFactor() > h.loadThres {\n        h.extend() // When load factor exceeds threshold, perform expansion\n    }\n    index := h.findBucket(key) // Search for bucket index corresponding to key\n    if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE {\n        h.buckets[index] = &pair{key, val} // If key-value pair does not exist, add the key-value pair\n        h.size++\n    } else {\n        h.buckets[index].val = val // If key-value pair found, overwrite val\n    }\n}\n\n/* Remove operation */\nfunc (h *hashMapOpenAddressing) remove(key int) {\n    index := h.findBucket(key) // Search for bucket index corresponding to key\n    if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE {\n        h.buckets[index] = h.TOMBSTONE // If key-value pair is found, overwrite it with removal marker\n        h.size--\n    }\n}\n\n/* Expand hash table */\nfunc (h *hashMapOpenAddressing) extend() {\n    oldBuckets := h.buckets               // Temporarily store the original hash table\n    h.capacity *= h.extendRatio           // Update capacity\n    h.buckets = make([]*pair, h.capacity) // Initialize expanded new hash table\n    h.size = 0                            // Reset size\n    // Move key-value pairs from original hash table to new hash table\n    for _, pair := range oldBuckets {\n        if pair != nil && pair != h.TOMBSTONE {\n            h.put(pair.key, pair.val)\n        }\n    }\n}\n\n/* Print hash table */\nfunc (h *hashMapOpenAddressing) print() {\n    for _, pair := range h.buckets {\n        if pair == nil {\n            fmt.Println(\"nil\")\n        } else if pair == h.TOMBSTONE {\n            fmt.Println(\"TOMBSTONE\")\n        } else {\n            fmt.Printf(\"%d -> %s\\n\", pair.key, pair.val)\n        }\n    }\n}\n
hash_map_open_addressing.swift
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    var loadThres: Double // Load factor threshold for triggering expansion\n    var extendRatio: Int // Expansion multiplier\n    var buckets: [Pair?] // Bucket array\n    var TOMBSTONE: Pair // Removal marker\n\n    /* Constructor */\n    init() {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = Array(repeating: nil, count: capacity)\n        TOMBSTONE = Pair(key: -1, val: \"-1\")\n    }\n\n    /* Hash function */\n    func hashFunc(key: Int) -> Int {\n        key % capacity\n    }\n\n    /* Load factor */\n    func loadFactor() -> Double {\n        Double(size) / Double(capacity)\n    }\n\n    /* Search for bucket index corresponding to key */\n    func findBucket(key: Int) -> Int {\n        var index = hashFunc(key: key)\n        var firstTombstone = -1\n        // Linear probing, break when encountering an empty bucket\n        while buckets[index] != nil {\n            // If key is encountered, return the corresponding bucket index\n            if buckets[index]!.key == key {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if firstTombstone != -1 {\n                    buckets[firstTombstone] = buckets[index]\n                    buckets[index] = TOMBSTONE\n                    return firstTombstone // Return the moved bucket index\n                }\n                return index // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if firstTombstone == -1 && buckets[index] == TOMBSTONE {\n                firstTombstone = index\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % capacity\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone\n    }\n\n    /* Query operation */\n    func get(key: Int) -> String? {\n        // Search for bucket index corresponding to key\n        let index = findBucket(key: key)\n        // If key-value pair is found, return corresponding val\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            return buckets[index]!.val\n        }\n        // If key-value pair does not exist, return null\n        return nil\n    }\n\n    /* Add operation */\n    func put(key: Int, val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if loadFactor() > loadThres {\n            extend()\n        }\n        // Search for bucket index corresponding to key\n        let index = findBucket(key: key)\n        // If key-value pair is found, overwrite val and return\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            buckets[index]!.val = val\n            return\n        }\n        // If key-value pair does not exist, add the key-value pair\n        buckets[index] = Pair(key: key, val: val)\n        size += 1\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        // Search for bucket index corresponding to key\n        let index = findBucket(key: key)\n        // If key-value pair is found, overwrite it with removal marker\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            buckets[index] = TOMBSTONE\n            size -= 1\n        }\n    }\n\n    /* Expand hash table */\n    func extend() {\n        // Temporarily store the original hash table\n        let bucketsTmp = buckets\n        // Initialize expanded new hash table\n        capacity *= extendRatio\n        buckets = Array(repeating: nil, count: capacity)\n        size = 0\n        // Move key-value pairs from original hash table to new hash table\n        for pair in bucketsTmp {\n            if let pair, pair != TOMBSTONE {\n                put(key: pair.key, val: pair.val)\n            }\n        }\n    }\n\n    /* Print hash table */\n    func print() {\n        for pair in buckets {\n            if pair == nil {\n                Swift.print(\"null\")\n            } else if pair == TOMBSTONE {\n                Swift.print(\"TOMBSTONE\")\n            } else {\n                Swift.print(\"\\(pair!.key) -> \\(pair!.val)\")\n            }\n        }\n    }\n}\n
hash_map_open_addressing.js
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    #size; // Number of key-value pairs\n    #capacity; // Hash table capacity\n    #loadThres; // Load factor threshold for triggering expansion\n    #extendRatio; // Expansion multiplier\n    #buckets; // Bucket array\n    #TOMBSTONE; // Removal marker\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0; // Number of key-value pairs\n        this.#capacity = 4; // Hash table capacity\n        this.#loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion\n        this.#extendRatio = 2; // Expansion multiplier\n        this.#buckets = Array(this.#capacity).fill(null); // Bucket array\n        this.#TOMBSTONE = new Pair(-1, '-1'); // Removal marker\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor() {\n        return this.#size / this.#capacity;\n    }\n\n    /* Search for bucket index corresponding to key */\n    #findBucket(key) {\n        let index = this.#hashFunc(key);\n        let firstTombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while (this.#buckets[index] !== null) {\n            // If key is encountered, return the corresponding bucket index\n            if (this.#buckets[index].key === key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone !== -1) {\n                    this.#buckets[firstTombstone] = this.#buckets[index];\n                    this.#buckets[index] = this.#TOMBSTONE;\n                    return firstTombstone; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (\n                firstTombstone === -1 &&\n                this.#buckets[index] === this.#TOMBSTONE\n            ) {\n                firstTombstone = index;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % this.#capacity;\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone === -1 ? index : firstTombstone;\n    }\n\n    /* Query operation */\n    get(key) {\n        // Search for bucket index corresponding to key\n        const index = this.#findBucket(key);\n        // If key-value pair is found, return corresponding val\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            return this.#buckets[index].val;\n        }\n        // If key-value pair does not exist, return null\n        return null;\n    }\n\n    /* Add operation */\n    put(key, val) {\n        // When load factor exceeds threshold, perform expansion\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        // Search for bucket index corresponding to key\n        const index = this.#findBucket(key);\n        // If key-value pair is found, overwrite val and return\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            this.#buckets[index].val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        this.#buckets[index] = new Pair(key, val);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key) {\n        // Search for bucket index corresponding to key\n        const index = this.#findBucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            this.#buckets[index] = this.#TOMBSTONE;\n            this.#size--;\n        }\n    }\n\n    /* Expand hash table */\n    #extend() {\n        // Temporarily store the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize expanded new hash table\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = Array(this.#capacity).fill(null);\n        this.#size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (const pair of bucketsTmp) {\n            if (pair !== null && pair !== this.#TOMBSTONE) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    print() {\n        for (const pair of this.#buckets) {\n            if (pair === null) {\n                console.log('null');\n            } else if (pair === this.#TOMBSTONE) {\n                console.log('TOMBSTONE');\n            } else {\n                console.log(pair.key + ' -> ' + pair.val);\n            }\n        }\n    }\n}\n
hash_map_open_addressing.ts
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private size: number; // Number of key-value pairs\n    private capacity: number; // Hash table capacity\n    private loadThres: number; // Load factor threshold for triggering expansion\n    private extendRatio: number; // Expansion multiplier\n    private buckets: Array<Pair | null>; // Bucket array\n    private TOMBSTONE: Pair; // Removal marker\n\n    /* Constructor */\n    constructor() {\n        this.size = 0; // Number of key-value pairs\n        this.capacity = 4; // Hash table capacity\n        this.loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion\n        this.extendRatio = 2; // Expansion multiplier\n        this.buckets = Array(this.capacity).fill(null); // Bucket array\n        this.TOMBSTONE = new Pair(-1, '-1'); // Removal marker\n    }\n\n    /* Hash function */\n    private hashFunc(key: number): number {\n        return key % this.capacity;\n    }\n\n    /* Load factor */\n    private loadFactor(): number {\n        return this.size / this.capacity;\n    }\n\n    /* Search for bucket index corresponding to key */\n    private findBucket(key: number): number {\n        let index = this.hashFunc(key);\n        let firstTombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while (this.buckets[index] !== null) {\n            // If key is encountered, return the corresponding bucket index\n            if (this.buckets[index]!.key === key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone !== -1) {\n                    this.buckets[firstTombstone] = this.buckets[index];\n                    this.buckets[index] = this.TOMBSTONE;\n                    return firstTombstone; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (\n                firstTombstone === -1 &&\n                this.buckets[index] === this.TOMBSTONE\n            ) {\n                firstTombstone = index;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % this.capacity;\n        }\n        // If key does not exist, return the index for insertion\n        return firstTombstone === -1 ? index : firstTombstone;\n    }\n\n    /* Query operation */\n    get(key: number): string | null {\n        // Search for bucket index corresponding to key\n        const index = this.findBucket(key);\n        // If key-value pair is found, return corresponding val\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            return this.buckets[index]!.val;\n        }\n        // If key-value pair does not exist, return null\n        return null;\n    }\n\n    /* Add operation */\n    put(key: number, val: string): void {\n        // When load factor exceeds threshold, perform expansion\n        if (this.loadFactor() > this.loadThres) {\n            this.extend();\n        }\n        // Search for bucket index corresponding to key\n        const index = this.findBucket(key);\n        // If key-value pair is found, overwrite val and return\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            this.buckets[index]!.val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        this.buckets[index] = new Pair(key, val);\n        this.size++;\n    }\n\n    /* Remove operation */\n    remove(key: number): void {\n        // Search for bucket index corresponding to key\n        const index = this.findBucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            this.buckets[index] = this.TOMBSTONE;\n            this.size--;\n        }\n    }\n\n    /* Expand hash table */\n    private extend(): void {\n        // Temporarily store the original hash table\n        const bucketsTmp = this.buckets;\n        // Initialize expanded new hash table\n        this.capacity *= this.extendRatio;\n        this.buckets = Array(this.capacity).fill(null);\n        this.size = 0;\n        // Move key-value pairs from original hash table to new hash table\n        for (const pair of bucketsTmp) {\n            if (pair !== null && pair !== this.TOMBSTONE) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print hash table */\n    print(): void {\n        for (const pair of this.buckets) {\n            if (pair === null) {\n                console.log('null');\n            } else if (pair === this.TOMBSTONE) {\n                console.log('TOMBSTONE');\n            } else {\n                console.log(pair.key + ' -> ' + pair.val);\n            }\n        }\n    }\n}\n
hash_map_open_addressing.dart
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n  late int _size; // Number of key-value pairs\n  int _capacity = 4; // Hash table capacity\n  double _loadThres = 2.0 / 3.0; // Load factor threshold for triggering expansion\n  int _extendRatio = 2; // Expansion multiplier\n  late List<Pair?> _buckets; // Bucket array\n  Pair _TOMBSTONE = Pair(-1, \"-1\"); // Removal marker\n\n  /* Constructor */\n  HashMapOpenAddressing() {\n    _size = 0;\n    _buckets = List.generate(_capacity, (index) => null);\n  }\n\n  /* Hash function */\n  int hashFunc(int key) {\n    return key % _capacity;\n  }\n\n  /* Load factor */\n  double loadFactor() {\n    return _size / _capacity;\n  }\n\n  /* Search for bucket index corresponding to key */\n  int findBucket(int key) {\n    int index = hashFunc(key);\n    int firstTombstone = -1;\n    // Linear probing, break when encountering an empty bucket\n    while (_buckets[index] != null) {\n      // If key is encountered, return the corresponding bucket index\n      if (_buckets[index]!.key == key) {\n        // If a removal marker was encountered before, move the key-value pair to that index\n        if (firstTombstone != -1) {\n          _buckets[firstTombstone] = _buckets[index];\n          _buckets[index] = _TOMBSTONE;\n          return firstTombstone; // Return the moved bucket index\n        }\n        return index; // Return bucket index\n      }\n      // Record the first removal marker encountered\n      if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) {\n        firstTombstone = index;\n      }\n      // Calculate bucket index, wrap around to the head if past the tail\n      index = (index + 1) % _capacity;\n    }\n    // If key does not exist, return the index for insertion\n    return firstTombstone == -1 ? index : firstTombstone;\n  }\n\n  /* Query operation */\n  String? get(int key) {\n    // Search for bucket index corresponding to key\n    int index = findBucket(key);\n    // If key-value pair is found, return corresponding val\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      return _buckets[index]!.val;\n    }\n    // If key-value pair does not exist, return null\n    return null;\n  }\n\n  /* Add operation */\n  void put(int key, String val) {\n    // When load factor exceeds threshold, perform expansion\n    if (loadFactor() > _loadThres) {\n      extend();\n    }\n    // Search for bucket index corresponding to key\n    int index = findBucket(key);\n    // If key-value pair is found, overwrite val and return\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      _buckets[index]!.val = val;\n      return;\n    }\n    // If key-value pair does not exist, add the key-value pair\n    _buckets[index] = new Pair(key, val);\n    _size++;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    // Search for bucket index corresponding to key\n    int index = findBucket(key);\n    // If key-value pair is found, overwrite it with removal marker\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      _buckets[index] = _TOMBSTONE;\n      _size--;\n    }\n  }\n\n  /* Expand hash table */\n  void extend() {\n    // Temporarily store the original hash table\n    List<Pair?> bucketsTmp = _buckets;\n    // Initialize expanded new hash table\n    _capacity *= _extendRatio;\n    _buckets = List.generate(_capacity, (index) => null);\n    _size = 0;\n    // Move key-value pairs from original hash table to new hash table\n    for (Pair? pair in bucketsTmp) {\n      if (pair != null && pair != _TOMBSTONE) {\n        put(pair.key, pair.val);\n      }\n    }\n  }\n\n  /* Print hash table */\n  void printHashMap() {\n    for (Pair? pair in _buckets) {\n      if (pair == null) {\n        print(\"null\");\n      } else if (pair == _TOMBSTONE) {\n        print(\"TOMBSTONE\");\n      } else {\n        print(\"${pair.key} -> ${pair.val}\");\n      }\n    }\n  }\n}\n
hash_map_open_addressing.rs
/* Hash table with open addressing */\nstruct HashMapOpenAddressing {\n    size: usize,                // Number of key-value pairs\n    capacity: usize,            // Hash table capacity\n    load_thres: f64,            // Load factor threshold for triggering expansion\n    extend_ratio: usize,        // Expansion multiplier\n    buckets: Vec<Option<Pair>>, // Bucket array\n    TOMBSTONE: Option<Pair>,    // Removal marker\n}\n\nimpl HashMapOpenAddressing {\n    /* Constructor */\n    fn new() -> Self {\n        Self {\n            size: 0,\n            capacity: 4,\n            load_thres: 2.0 / 3.0,\n            extend_ratio: 2,\n            buckets: vec![None; 4],\n            TOMBSTONE: Some(Pair {\n                key: -1,\n                val: \"-1\".to_string(),\n            }),\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        (key % self.capacity as i32) as usize\n    }\n\n    /* Load factor */\n    fn load_factor(&self) -> f64 {\n        self.size as f64 / self.capacity as f64\n    }\n\n    /* Search for bucket index corresponding to key */\n    fn find_bucket(&mut self, key: i32) -> usize {\n        let mut index = self.hash_func(key);\n        let mut first_tombstone = -1;\n        // Linear probing, break when encountering an empty bucket\n        while self.buckets[index].is_some() {\n            // If key is found, return corresponding bucket index\n            if self.buckets[index].as_ref().unwrap().key == key {\n                // If deletion marker was encountered before, move key-value pair to that index\n                if first_tombstone != -1 {\n                    self.buckets[first_tombstone as usize] = self.buckets[index].take();\n                    self.buckets[index] = self.TOMBSTONE.clone();\n                    return first_tombstone as usize; // Return the moved bucket index\n                }\n                return index; // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE {\n                first_tombstone = index as i32;\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % self.capacity;\n        }\n        // If key does not exist, return the index for insertion\n        if first_tombstone == -1 {\n            index\n        } else {\n            first_tombstone as usize\n        }\n    }\n\n    /* Query operation */\n    fn get(&mut self, key: i32) -> Option<&str> {\n        // Search for bucket index corresponding to key\n        let index = self.find_bucket(key);\n        // If key-value pair is found, return corresponding val\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            return self.buckets[index].as_ref().map(|pair| &pair.val as &str);\n        }\n        // If key-value pair does not exist, return null\n        None\n    }\n\n    /* Add operation */\n    fn put(&mut self, key: i32, val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if self.load_factor() > self.load_thres {\n            self.extend();\n        }\n        // Search for bucket index corresponding to key\n        let index = self.find_bucket(key);\n        // If key-value pair is found, overwrite val and return\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            self.buckets[index].as_mut().unwrap().val = val;\n            return;\n        }\n        // If key-value pair does not exist, add the key-value pair\n        self.buckets[index] = Some(Pair { key, val });\n        self.size += 1;\n    }\n\n    /* Remove operation */\n    fn remove(&mut self, key: i32) {\n        // Search for bucket index corresponding to key\n        let index = self.find_bucket(key);\n        // If key-value pair is found, overwrite it with removal marker\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            self.buckets[index] = self.TOMBSTONE.clone();\n            self.size -= 1;\n        }\n    }\n\n    /* Expand hash table */\n    fn extend(&mut self) {\n        // Temporarily store the original hash table\n        let buckets_tmp = self.buckets.clone();\n        // Initialize expanded new hash table\n        self.capacity *= self.extend_ratio;\n        self.buckets = vec![None; self.capacity];\n        self.size = 0;\n\n        // Move key-value pairs from original hash table to new hash table\n        for pair in buckets_tmp {\n            if pair.is_none() || pair == self.TOMBSTONE {\n                continue;\n            }\n            let pair = pair.unwrap();\n\n            self.put(pair.key, pair.val);\n        }\n    }\n    /* Print hash table */\n    fn print(&self) {\n        for pair in &self.buckets {\n            if pair.is_none() {\n                println!(\"null\");\n            } else if pair == &self.TOMBSTONE {\n                println!(\"TOMBSTONE\");\n            } else {\n                let pair = pair.as_ref().unwrap();\n                println!(\"{} -> {}\", pair.key, pair.val);\n            }\n        }\n    }\n}\n
hash_map_open_addressing.c
/* Hash table with open addressing */\ntypedef struct {\n    int size;         // Number of key-value pairs\n    int capacity;     // Hash table capacity\n    double loadThres; // Load factor threshold for triggering expansion\n    int extendRatio;  // Expansion multiplier\n    Pair **buckets;   // Bucket array\n    Pair *TOMBSTONE;  // Removal marker\n} HashMapOpenAddressing;\n\n/* Constructor */\nHashMapOpenAddressing *newHashMapOpenAddressing() {\n    HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing));\n    hashMap->size = 0;\n    hashMap->capacity = 4;\n    hashMap->loadThres = 2.0 / 3.0;\n    hashMap->extendRatio = 2;\n    hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));\n    hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair));\n    hashMap->TOMBSTONE->key = -1;\n    hashMap->TOMBSTONE->val = \"-1\";\n\n    return hashMap;\n}\n\n/* Destructor */\nvoid delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Pair *pair = hashMap->buckets[i];\n        if (pair != NULL && pair != hashMap->TOMBSTONE) {\n            free(pair->val);\n            free(pair);\n        }\n    }\n    free(hashMap->buckets);\n    free(hashMap->TOMBSTONE);\n    free(hashMap);\n}\n\n/* Hash function */\nint hashFunc(HashMapOpenAddressing *hashMap, int key) {\n    return key % hashMap->capacity;\n}\n\n/* Load factor */\ndouble loadFactor(HashMapOpenAddressing *hashMap) {\n    return (double)hashMap->size / (double)hashMap->capacity;\n}\n\n/* Search for bucket index corresponding to key */\nint findBucket(HashMapOpenAddressing *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    int firstTombstone = -1;\n    // Linear probing, break when encountering an empty bucket\n    while (hashMap->buckets[index] != NULL) {\n        // If key is encountered, return the corresponding bucket index\n        if (hashMap->buckets[index]->key == key) {\n            // If a removal marker was encountered before, move the key-value pair to that index\n            if (firstTombstone != -1) {\n                hashMap->buckets[firstTombstone] = hashMap->buckets[index];\n                hashMap->buckets[index] = hashMap->TOMBSTONE;\n                return firstTombstone; // Return the moved bucket index\n            }\n            return index; // Return bucket index\n        }\n        // Record the first removal marker encountered\n        if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) {\n            firstTombstone = index;\n        }\n        // Calculate bucket index, wrap around to the head if past the tail\n        index = (index + 1) % hashMap->capacity;\n    }\n    // If key does not exist, return the index for insertion\n    return firstTombstone == -1 ? index : firstTombstone;\n}\n\n/* Query operation */\nchar *get(HashMapOpenAddressing *hashMap, int key) {\n    // Search for bucket index corresponding to key\n    int index = findBucket(hashMap, key);\n    // If key-value pair is found, return corresponding val\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        return hashMap->buckets[index]->val;\n    }\n    // Return empty string if key-value pair does not exist\n    return \"\";\n}\n\n/* Add operation */\nvoid put(HashMapOpenAddressing *hashMap, int key, char *val) {\n    // When load factor exceeds threshold, perform expansion\n    if (loadFactor(hashMap) > hashMap->loadThres) {\n        extend(hashMap);\n    }\n    // Search for bucket index corresponding to key\n    int index = findBucket(hashMap, key);\n    // If key-value pair is found, overwrite val and return\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        free(hashMap->buckets[index]->val);\n        hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1));\n        strcpy(hashMap->buckets[index]->val, val);\n        hashMap->buckets[index]->val[strlen(val)] = '\\0';\n        return;\n    }\n    // If key-value pair does not exist, add the key-value pair\n    Pair *pair = (Pair *)malloc(sizeof(Pair));\n    pair->key = key;\n    pair->val = (char *)malloc(sizeof(strlen(val) + 1));\n    strcpy(pair->val, val);\n    pair->val[strlen(val)] = '\\0';\n\n    hashMap->buckets[index] = pair;\n    hashMap->size++;\n}\n\n/* Remove operation */\nvoid removeItem(HashMapOpenAddressing *hashMap, int key) {\n    // Search for bucket index corresponding to key\n    int index = findBucket(hashMap, key);\n    // If key-value pair is found, overwrite it with removal marker\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        Pair *pair = hashMap->buckets[index];\n        free(pair->val);\n        free(pair);\n        hashMap->buckets[index] = hashMap->TOMBSTONE;\n        hashMap->size--;\n    }\n}\n\n/* Expand hash table */\nvoid extend(HashMapOpenAddressing *hashMap) {\n    // Temporarily store the original hash table\n    Pair **bucketsTmp = hashMap->buckets;\n    int oldCapacity = hashMap->capacity;\n    // Initialize expanded new hash table\n    hashMap->capacity *= hashMap->extendRatio;\n    hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));\n    hashMap->size = 0;\n    // Move key-value pairs from original hash table to new hash table\n    for (int i = 0; i < oldCapacity; i++) {\n        Pair *pair = bucketsTmp[i];\n        if (pair != NULL && pair != hashMap->TOMBSTONE) {\n            put(hashMap, pair->key, pair->val);\n            free(pair->val);\n            free(pair);\n        }\n    }\n    free(bucketsTmp);\n}\n\n/* Print hash table */\nvoid print(HashMapOpenAddressing *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Pair *pair = hashMap->buckets[i];\n        if (pair == NULL) {\n            printf(\"NULL\\n\");\n        } else if (pair == hashMap->TOMBSTONE) {\n            printf(\"TOMBSTONE\\n\");\n        } else {\n            printf(\"%d -> %s\\n\", pair->key, pair->val);\n        }\n    }\n}\n
hash_map_open_addressing.kt
/* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private var size: Int               // Number of key-value pairs\n    private var capacity: Int           // Hash table capacity\n    private val loadThres: Double       // Load factor threshold for triggering expansion\n    private val extendRatio: Int        // Expansion multiplier\n    private var buckets: Array<Pair?>   // Bucket array\n    private val TOMBSTONE: Pair         // Removal marker\n\n    /* Constructor */\n    init {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = arrayOfNulls(capacity)\n        TOMBSTONE = Pair(-1, \"-1\")\n    }\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        return key % capacity\n    }\n\n    /* Load factor */\n    fun loadFactor(): Double {\n        return (size / capacity).toDouble()\n    }\n\n    /* Search for bucket index corresponding to key */\n    fun findBucket(key: Int): Int {\n        var index = hashFunc(key)\n        var firstTombstone = -1\n        // Linear probing, break when encountering an empty bucket\n        while (buckets[index] != null) {\n            // If key is encountered, return the corresponding bucket index\n            if (buckets[index]?.key == key) {\n                // If a removal marker was encountered before, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index]\n                    buckets[index] = TOMBSTONE\n                    return firstTombstone // Return the moved bucket index\n                }\n                return index // Return bucket index\n            }\n            // Record the first removal marker encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index\n            }\n            // Calculate bucket index, wrap around to the head if past the tail\n            index = (index + 1) % capacity\n        }\n        // If key does not exist, return the index for insertion\n        return if (firstTombstone == -1) index else firstTombstone\n    }\n\n    /* Query operation */\n    fun get(key: Int): String? {\n        // Search for bucket index corresponding to key\n        val index = findBucket(key)\n        // If key-value pair is found, return corresponding val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index]?._val\n        }\n        // If key-value pair does not exist, return null\n        return null\n    }\n\n    /* Add operation */\n    fun put(key: Int, _val: String) {\n        // When load factor exceeds threshold, perform expansion\n        if (loadFactor() > loadThres) {\n            extend()\n        }\n        // Search for bucket index corresponding to key\n        val index = findBucket(key)\n        // If key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index]!!._val = _val\n            return\n        }\n        // If key-value pair does not exist, add the key-value pair\n        buckets[index] = Pair(key, _val)\n        size++\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        // Search for bucket index corresponding to key\n        val index = findBucket(key)\n        // If key-value pair is found, overwrite it with removal marker\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE\n            size--\n        }\n    }\n\n    /* Expand hash table */\n    fun extend() {\n        // Temporarily store the original hash table\n        val bucketsTmp = buckets\n        // Initialize expanded new hash table\n        capacity *= extendRatio\n        buckets = arrayOfNulls(capacity)\n        size = 0\n        // Move key-value pairs from original hash table to new hash table\n        for (pair in bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                put(pair.key, pair._val)\n            }\n        }\n    }\n\n    /* Print hash table */\n    fun print() {\n        for (pair in buckets) {\n            if (pair == null) {\n                println(\"null\")\n            } else if (pair == TOMBSTONE) {\n                println(\"TOMESTOME\")\n            } else {\n                println(\"${pair.key} -> ${pair._val}\")\n            }\n        }\n    }\n}\n
hash_map_open_addressing.rb
### Hash map with open addressing ###\nclass HashMapOpenAddressing\n  TOMBSTONE = Pair.new(-1, '-1') # Removal marker\n\n  ### Constructor ###\n  def initialize\n    @size = 0 # Number of key-value pairs\n    @capacity = 4 # Hash table capacity\n    @load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion\n    @extend_ratio = 2 # Expansion multiplier\n    @buckets = Array.new(@capacity) # Bucket array\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    key % @capacity\n  end\n\n  ### Load factor ###\n  def load_factor\n    @size / @capacity\n  end\n\n  ### Search bucket index for key ###\n  def find_bucket(key)\n    index = hash_func(key)\n    first_tombstone = -1\n    # Linear probing, break when encountering an empty bucket\n    while !@buckets[index].nil?\n      # If key is encountered, return the corresponding bucket index\n      if @buckets[index].key == key\n        # If a removal marker was encountered before, move the key-value pair to that index\n        if first_tombstone != -1\n          @buckets[first_tombstone] = @buckets[index]\n          @buckets[index] = TOMBSTONE\n          return first_tombstone # Return the moved bucket index\n        end\n        return index # Return bucket index\n      end\n      # Record the first removal marker encountered\n      first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE\n      # Calculate bucket index, wrap around to the head if past the tail\n      index = (index + 1) % @capacity\n    end\n    # If key does not exist, return the index for insertion\n    first_tombstone == -1 ? index : first_tombstone\n  end\n\n  ### Query operation ###\n  def get(key)\n    # Search for bucket index corresponding to key\n    index = find_bucket(key)\n    # If key-value pair is found, return corresponding val\n    return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index])\n    # Return nil if key-value pair does not exist\n    nil\n  end\n\n  ### Add operation ###\n  def put(key, val)\n    # When load factor exceeds threshold, perform expansion\n    extend if load_factor > @load_thres\n    # Search for bucket index corresponding to key\n    index = find_bucket(key)\n    # If key-value pair found, overwrite val and return\n    unless [nil, TOMBSTONE].include?(@buckets[index])\n      @buckets[index].val = val\n      return\n    end\n    # If key-value pair does not exist, add the key-value pair\n    @buckets[index] = Pair.new(key, val)\n    @size += 1\n  end\n\n  ### Delete operation ###\n  def remove(key)\n    # Search for bucket index corresponding to key\n    index = find_bucket(key)\n    # If key-value pair is found, overwrite it with removal marker\n    unless [nil, TOMBSTONE].include?(@buckets[index])\n      @buckets[index] = TOMBSTONE\n      @size -= 1\n    end\n  end\n\n  ### Expand hash table ###\n  def extend\n    # Temporarily store the original hash table\n    buckets_tmp = @buckets\n    # Initialize expanded new hash table\n    @capacity *= @extend_ratio\n    @buckets = Array.new(@capacity)\n    @size = 0\n    # Move key-value pairs from original hash table to new hash table\n    for pair in buckets_tmp\n      put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair)\n    end\n  end\n\n  ### Print hash table ###\n  def print\n    for pair in @buckets\n      if pair.nil?\n        puts \"Nil\"\n      elsif pair == TOMBSTONE\n        puts \"TOMBSTONE\"\n      else\n        puts \"#{pair.key} -> #{pair.val}\"\n      end\n    end\n  end\nend\n
","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#2-quadratic-probing","level":3,"title":"2.   Quadratic Probing","text":"

Quadratic probing is similar to linear probing and is one of the common strategies for open addressing. When a collision occurs, quadratic probing does not simply skip a fixed number of steps but skips a number of steps equal to the \"square of the number of probes\", i.e., \\(1, 4, 9, \\dots\\) steps.

Quadratic probing has the following advantages:

  • Quadratic probing attempts to alleviate the clustering effect of linear probing by skipping distances equal to the square of the probe count.
  • Quadratic probing skips larger distances to find empty positions, which helps to distribute data more evenly.

However, quadratic probing is not perfect:

  • Clustering still exists, i.e., some positions are more likely to be occupied than others.
  • Due to the growth of squares, quadratic probing may not probe the entire hash table, meaning that even if there are empty buckets in the hash table, quadratic probing may not be able to access them.
","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#3-double-hashing","level":3,"title":"3.   Double Hashing","text":"

As the name suggests, the double hashing method uses multiple hash functions \\(f_1(x)\\), \\(f_2(x)\\), \\(f_3(x)\\), \\(\\dots\\) for probing.

  • Inserting elements: If hash function \\(f_1(x)\\) encounters a conflict, try \\(f_2(x)\\), and so on, until an empty position is found and the element is inserted.
  • Searching for elements: Search in the same order of hash functions until the target element is found and return it; if an empty position is encountered or all hash functions have been tried, it indicates the element is not in the hash table, then return None.

Compared to linear probing, the double hashing method is less prone to clustering, but multiple hash functions introduce additional computational overhead.

Tip

Please note that open addressing (linear probing, quadratic probing, and double hashing) hash tables all have the problem of \"cannot directly delete elements\".

","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_collision/#623-choice-of-programming-languages","level":2,"title":"6.2.3   Choice of Programming Languages","text":"

Different programming languages adopt different hash table implementation strategies. Here are a few examples:

  • Python uses open addressing. The dict dictionary uses pseudo-random numbers for probing.
  • Java uses separate chaining. Since JDK 1.8, when the array length in HashMap reaches 64 and the length of a linked list reaches 8, the linked list is converted to a red-black tree to improve search performance.
  • Go uses separate chaining. Go stipulates that each bucket can store up to 8 key-value pairs, and if the capacity is exceeded, an overflow bucket is linked; when there are too many overflow buckets, a special equal-capacity expansion operation is performed to ensure performance.
","path":["Chapter 6. Hashing","6.2   Hash Collision"],"tags":[]},{"location":"chapter_hashing/hash_map/","level":1,"title":"6.1   Hash Table","text":"

A hash table, also known as a hash map, establishes a mapping between keys key and values value, enabling efficient element retrieval. Specifically, when we input a key key into a hash table, we can retrieve the corresponding value value in \\(O(1)\\) time.

As shown in Figure 6-1, given \\(n\\) students, each with two pieces of data: \"name\" and \"student ID\". If we want to implement a query function that \"inputs a student ID and returns the corresponding name\", we can use the hash table shown below.

Figure 6-1   Abstract representation of a hash table

In addition to hash tables, arrays and linked lists can also implement query functionality. Their efficiency comparison is shown in the following table.

  • Adding elements: Simply add elements to the end of the array (linked list), using \\(O(1)\\) time.
  • Querying elements: Since the array (linked list) is unordered, all elements need to be traversed, using \\(O(n)\\) time.
  • Deleting elements: The element must first be located, then deleted from the array (linked list), using \\(O(n)\\) time.

Table 6-1   Comparison of element query efficiency

Array Linked List Hash Table Find element \\(O(n)\\) \\(O(n)\\) \\(O(1)\\) Add element \\(O(1)\\) \\(O(1)\\) \\(O(1)\\) Delete element \\(O(n)\\) \\(O(n)\\) \\(O(1)\\)

As observed, the time complexity for insertion, deletion, search, and modification operations in a hash table is \\(O(1)\\), which is very efficient.

","path":["Chapter 6. Hashing","6.1   Hash Table"],"tags":[]},{"location":"chapter_hashing/hash_map/#611-common-hash-table-operations","level":2,"title":"6.1.1   Common Hash Table Operations","text":"

Common operations on hash tables include: initialization, query operations, adding key-value pairs, and deleting key-value pairs. Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map.py
# Initialize hash table\nhmap: dict = {}\n\n# Add operation\n# Add key-value pair (key, value) to hash table\nhmap[12836] = \"XiaoHa\"\nhmap[15937] = \"XiaoLuo\"\nhmap[16750] = \"XiaoSuan\"\nhmap[13276] = \"XiaoFa\"\nhmap[10583] = \"XiaoYa\"\n\n# Query operation\n# Input key into hash table to get value\nname: str = hmap[15937]\n\n# Delete operation\n# Delete key-value pair (key, value) from hash table\nhmap.pop(10583)\n
hash_map.cpp
/* Initialize hash table */\nunordered_map<int, string> map;\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap[12836] = \"XiaoHa\";\nmap[15937] = \"XiaoLuo\";\nmap[16750] = \"XiaoSuan\";\nmap[13276] = \"XiaoFa\";\nmap[10583] = \"XiaoYa\";\n\n/* Query operation */\n// Input key into hash table to get value\nstring name = map[15937];\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.erase(10583);\n
hash_map.java
/* Initialize hash table */\nMap<Integer, String> map = new HashMap<>();\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap.put(12836, \"XiaoHa\");\nmap.put(15937, \"XiaoLuo\");\nmap.put(16750, \"XiaoSuan\");\nmap.put(13276, \"XiaoFa\");\nmap.put(10583, \"XiaoYa\");\n\n/* Query operation */\n// Input key into hash table to get value\nString name = map.get(15937);\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.remove(10583);\n
hash_map.cs
/* Initialize hash table */\nDictionary<int, string> map = new() {\n    /* Add operation */\n    // Add key-value pair (key, value) to hash table\n    { 12836, \"XiaoHa\" },\n    { 15937, \"XiaoLuo\" },\n    { 16750, \"XiaoSuan\" },\n    { 13276, \"XiaoFa\" },\n    { 10583, \"XiaoYa\" }\n};\n\n/* Query operation */\n// Input key into hash table to get value\nstring name = map[15937];\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.Remove(10583);\n
hash_map_test.go
/* Initialize hash table */\nhmap := make(map[int]string)\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nhmap[12836] = \"XiaoHa\"\nhmap[15937] = \"XiaoLuo\"\nhmap[16750] = \"XiaoSuan\"\nhmap[13276] = \"XiaoFa\"\nhmap[10583] = \"XiaoYa\"\n\n/* Query operation */\n// Input key into hash table to get value\nname := hmap[15937]\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\ndelete(hmap, 10583)\n
hash_map.swift
/* Initialize hash table */\nvar map: [Int: String] = [:]\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap[12836] = \"XiaoHa\"\nmap[15937] = \"XiaoLuo\"\nmap[16750] = \"XiaoSuan\"\nmap[13276] = \"XiaoFa\"\nmap[10583] = \"XiaoYa\"\n\n/* Query operation */\n// Input key into hash table to get value\nlet name = map[15937]!\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.removeValue(forKey: 10583)\n
hash_map.js
/* Initialize hash table */\nconst map = new Map();\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap.set(12836, 'XiaoHa');\nmap.set(15937, 'XiaoLuo');\nmap.set(16750, 'XiaoSuan');\nmap.set(13276, 'XiaoFa');\nmap.set(10583, 'XiaoYa');\n\n/* Query operation */\n// Input key into hash table to get value\nlet name = map.get(15937);\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.delete(10583);\n
hash_map.ts
/* Initialize hash table */\nconst map = new Map<number, string>();\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap.set(12836, 'XiaoHa');\nmap.set(15937, 'XiaoLuo');\nmap.set(16750, 'XiaoSuan');\nmap.set(13276, 'XiaoFa');\nmap.set(10583, 'XiaoYa');\nconsole.info('\\nAfter adding, hash table is\\nKey -> Value');\nconsole.info(map);\n\n/* Query operation */\n// Input key into hash table to get value\nlet name = map.get(15937);\nconsole.info('\\nInput student ID 15937, queried name ' + name);\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.delete(10583);\nconsole.info('\\nAfter deleting 10583, hash table is\\nKey -> Value');\nconsole.info(map);\n
hash_map.dart
/* Initialize hash table */\nMap<int, String> map = {};\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap[12836] = \"XiaoHa\";\nmap[15937] = \"XiaoLuo\";\nmap[16750] = \"XiaoSuan\";\nmap[13276] = \"XiaoFa\";\nmap[10583] = \"XiaoYa\";\n\n/* Query operation */\n// Input key into hash table to get value\nString name = map[15937];\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.remove(10583);\n
hash_map.rs
use std::collections::HashMap;\n\n/* Initialize hash table */\nlet mut map: HashMap<i32, String> = HashMap::new();\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap.insert(12836, \"XiaoHa\".to_string());\nmap.insert(15937, \"XiaoLuo\".to_string());\nmap.insert(16750, \"XiaoSuan\".to_string());\nmap.insert(13279, \"XiaoFa\".to_string());\nmap.insert(10583, \"XiaoYa\".to_string());\n\n/* Query operation */\n// Input key into hash table to get value\nlet _name: Option<&String> = map.get(&15937);\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nlet _removed_value: Option<String> = map.remove(&10583);\n
hash_map.c
// C does not provide a built-in hash table\n
hash_map.kt
/* Initialize hash table */\nval map = HashMap<Int,String>()\n\n/* Add operation */\n// Add key-value pair (key, value) to hash table\nmap[12836] = \"XiaoHa\"\nmap[15937] = \"XiaoLuo\"\nmap[16750] = \"XiaoSuan\"\nmap[13276] = \"XiaoFa\"\nmap[10583] = \"XiaoYa\"\n\n/* Query operation */\n// Input key into hash table to get value\nval name = map[15937]\n\n/* Delete operation */\n// Delete key-value pair (key, value) from hash table\nmap.remove(10583)\n
hash_map.rb
# Initialize hash table\nhmap = {}\n\n# Add operation\n# Add key-value pair (key, value) to hash table\nhmap[12836] = \"XiaoHa\"\nhmap[15937] = \"XiaoLuo\"\nhmap[16750] = \"XiaoSuan\"\nhmap[13276] = \"XiaoFa\"\nhmap[10583] = \"XiaoYa\"\n\n# Query operation\n# Input key into hash table to get value\nname = hmap[15937]\n\n# Delete operation\n# Delete key-value pair (key, value) from hash table\nhmap.delete(10583)\n
Visualized Execution

https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

There are three common ways to traverse a hash table: traversing key-value pairs, traversing keys, and traversing values. Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map.py
# Traverse hash table\n# Traverse key-value pairs key->value\nfor key, value in hmap.items():\n    print(key, \"->\", value)\n# Traverse keys only\nfor key in hmap.keys():\n    print(key)\n# Traverse values only\nfor value in hmap.values():\n    print(value)\n
hash_map.cpp
/* Traverse hash table */\n// Traverse key-value pairs key->value\nfor (auto kv: map) {\n    cout << kv.first << \" -> \" << kv.second << endl;\n}\n// Traverse using iterator key->value\nfor (auto iter = map.begin(); iter != map.end(); iter++) {\n    cout << iter->first << \"->\" << iter->second << endl;\n}\n
hash_map.java
/* Traverse hash table */\n// Traverse key-value pairs key->value\nfor (Map.Entry<Integer, String> kv: map.entrySet()) {\n    System.out.println(kv.getKey() + \" -> \" + kv.getValue());\n}\n// Traverse keys only\nfor (int key: map.keySet()) {\n    System.out.println(key);\n}\n// Traverse values only\nfor (String val: map.values()) {\n    System.out.println(val);\n}\n
hash_map.cs
/* Traverse hash table */\n// Traverse key-value pairs Key->Value\nforeach (var kv in map) {\n    Console.WriteLine(kv.Key + \" -> \" + kv.Value);\n}\n// Traverse keys only\nforeach (int key in map.Keys) {\n    Console.WriteLine(key);\n}\n// Traverse values only\nforeach (string val in map.Values) {\n    Console.WriteLine(val);\n}\n
hash_map_test.go
/* Traverse hash table */\n// Traverse key-value pairs key->value\nfor key, value := range hmap {\n    fmt.Println(key, \"->\", value)\n}\n// Traverse keys only\nfor key := range hmap {\n    fmt.Println(key)\n}\n// Traverse values only\nfor _, value := range hmap {\n    fmt.Println(value)\n}\n
hash_map.swift
/* Traverse hash table */\n// Traverse key-value pairs Key->Value\nfor (key, value) in map {\n    print(\"\\(key) -> \\(value)\")\n}\n// Traverse keys only\nfor key in map.keys {\n    print(key)\n}\n// Traverse values only\nfor value in map.values {\n    print(value)\n}\n
hash_map.js
/* Traverse hash table */\nconsole.info('\\nTraverse key-value pairs Key->Value');\nfor (const [k, v] of map.entries()) {\n    console.info(k + ' -> ' + v);\n}\nconsole.info('\\nTraverse keys only Key');\nfor (const k of map.keys()) {\n    console.info(k);\n}\nconsole.info('\\nTraverse values only Value');\nfor (const v of map.values()) {\n    console.info(v);\n}\n
hash_map.ts
/* Traverse hash table */\nconsole.info('\\nTraverse key-value pairs Key->Value');\nfor (const [k, v] of map.entries()) {\n    console.info(k + ' -> ' + v);\n}\nconsole.info('\\nTraverse keys only Key');\nfor (const k of map.keys()) {\n    console.info(k);\n}\nconsole.info('\\nTraverse values only Value');\nfor (const v of map.values()) {\n    console.info(v);\n}\n
hash_map.dart
/* Traverse hash table */\n// Traverse key-value pairs Key->Value\nmap.forEach((key, value) {\n  print('$key -> $value');\n});\n\n// Traverse keys only\nmap.keys.forEach((key) {\n  print(key);\n});\n\n// Traverse values only\nmap.values.forEach((value) {\n  print(value);\n});\n
hash_map.rs
/* Traverse hash table */\n// Traverse key-value pairs Key->Value\nfor (key, value) in &map {\n    println!(\"{key} -> {value}\");\n}\n\n// Traverse keys only\nfor key in map.keys() {\n    println!(\"{key}\");\n}\n\n// Traverse values only\nfor value in map.values() {\n    println!(\"{value}\");\n}\n
hash_map.c
// C does not provide a built-in hash table\n
hash_map.kt
/* Traverse hash table */\n// Traverse key-value pairs key->value\nfor ((key, value) in map) {\n    println(\"$key -> $value\")\n}\n// Traverse keys only\nfor (key in map.keys) {\n    println(key)\n}\n// Traverse values only\nfor (_val in map.values) {\n    println(_val)\n}\n
hash_map.rb
# Traverse hash table\n# Traverse key-value pairs key->value\nhmap.entries.each { |key, value| puts \"#{key} -> #{value}\" }\n\n# Traverse keys only\nhmap.keys.each { |key| puts key }\n\n# Traverse values only\nhmap.values.each { |val| puts val }\n
Visualized Execution

https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

","path":["Chapter 6. Hashing","6.1   Hash Table"],"tags":[]},{"location":"chapter_hashing/hash_map/#612-simple-hash-table-implementation","level":2,"title":"6.1.2   Simple Hash Table Implementation","text":"

Let's first consider the simplest case: implementing a hash table using only an array. In a hash table, each empty position in the array is called a bucket, and each bucket can store a key-value pair. Therefore, the query operation is to find the bucket corresponding to key and retrieve the value from the bucket.

So how do we locate the corresponding bucket based on key? This is achieved through a hash function. The role of the hash function is to map a larger input space to a smaller output space. In a hash table, the input space is all keys, and the output space is all buckets (array indices). In other words, given a key, we can use the hash function to obtain the storage location of the key-value pair corresponding to that key in the array.

When inputting a key, the hash function's calculation process consists of the following two steps:

  1. Calculate the hash value through a hash algorithm hash().
  2. Take the modulo of the hash value by the number of buckets (array length) capacity to obtain the bucket (array index) index corresponding to that key.
index = hash(key) % capacity\n

Subsequently, we can use index to access the corresponding bucket in the hash table and retrieve the value.

Assuming the array length is capacity = 100 and the hash algorithm is hash(key) = key, the hash function becomes key % 100. Figure 6-2 shows the working principle of the hash function using key as student ID and value as name.

Figure 6-2   Working principle of hash function

The following code implements a simple hash table. Here, we encapsulate key and value into a class Pair to represent a key-value pair.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_hash_map.py
class Pair:\n    \"\"\"Key-value pair\"\"\"\n\n    def __init__(self, key: int, val: str):\n        self.key = key\n        self.val = val\n\nclass ArrayHashMap:\n    \"\"\"Hash table based on array implementation\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        # Initialize array with 100 buckets\n        self.buckets: list[Pair | None] = [None] * 100\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        index = key % 100\n        return index\n\n    def get(self, key: int) -> str | None:\n        \"\"\"Query operation\"\"\"\n        index: int = self.hash_func(key)\n        pair: Pair = self.buckets[index]\n        if pair is None:\n            return None\n        return pair.val\n\n    def put(self, key: int, val: str):\n        \"\"\"Add and update operation\"\"\"\n        pair = Pair(key, val)\n        index: int = self.hash_func(key)\n        self.buckets[index] = pair\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        index: int = self.hash_func(key)\n        # Set to None to represent removal\n        self.buckets[index] = None\n\n    def entry_set(self) -> list[Pair]:\n        \"\"\"Get all key-value pairs\"\"\"\n        result: list[Pair] = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair)\n        return result\n\n    def key_set(self) -> list[int]:\n        \"\"\"Get all keys\"\"\"\n        result = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair.key)\n        return result\n\n    def value_set(self) -> list[str]:\n        \"\"\"Get all values\"\"\"\n        result = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair.val)\n        return result\n\n    def print(self):\n        \"\"\"Print hash table\"\"\"\n        for pair in self.buckets:\n            if pair is not None:\n                print(pair.key, \"->\", pair.val)\n
array_hash_map.cpp
/* Key-value pair */\nstruct Pair {\n  public:\n    int key;\n    string val;\n    Pair(int key, string val) {\n        this->key = key;\n        this->val = val;\n    }\n};\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n  private:\n    vector<Pair *> buckets;\n\n  public:\n    ArrayHashMap() {\n        // Initialize array with 100 buckets\n        buckets = vector<Pair *>(100);\n    }\n\n    ~ArrayHashMap() {\n        // Free memory\n        for (const auto &bucket : buckets) {\n            delete bucket;\n        }\n        buckets.clear();\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Query operation */\n    string get(int key) {\n        int index = hashFunc(key);\n        Pair *pair = buckets[index];\n        if (pair == nullptr)\n            return \"\";\n        return pair->val;\n    }\n\n    /* Add operation */\n    void put(int key, string val) {\n        Pair *pair = new Pair(key, val);\n        int index = hashFunc(key);\n        buckets[index] = pair;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        // Free memory and set to nullptr\n        delete buckets[index];\n        buckets[index] = nullptr;\n    }\n\n    /* Get all key-value pairs */\n    vector<Pair *> pairSet() {\n        vector<Pair *> pairSet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                pairSet.push_back(pair);\n            }\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    vector<int> keySet() {\n        vector<int> keySet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                keySet.push_back(pair->key);\n            }\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    vector<string> valueSet() {\n        vector<string> valueSet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                valueSet.push_back(pair->val);\n            }\n        }\n        return valueSet;\n    }\n\n    /* Print hash table */\n    void print() {\n        for (Pair *kv : pairSet()) {\n            cout << kv->key << \" -> \" << kv->val << endl;\n        }\n    }\n};\n
array_hash_map.java
/* Key-value pair */\nclass Pair {\n    public int key;\n    public String val;\n\n    public Pair(int key, String val) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    private List<Pair> buckets;\n\n    public ArrayHashMap() {\n        // Initialize array with 100 buckets\n        buckets = new ArrayList<>();\n        for (int i = 0; i < 100; i++) {\n            buckets.add(null);\n        }\n    }\n\n    /* Hash function */\n    private int hashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Query operation */\n    public String get(int key) {\n        int index = hashFunc(key);\n        Pair pair = buckets.get(index);\n        if (pair == null)\n            return null;\n        return pair.val;\n    }\n\n    /* Add operation */\n    public void put(int key, String val) {\n        Pair pair = new Pair(key, val);\n        int index = hashFunc(key);\n        buckets.set(index, pair);\n    }\n\n    /* Remove operation */\n    public void remove(int key) {\n        int index = hashFunc(key);\n        // Set to null to represent deletion\n        buckets.set(index, null);\n    }\n\n    /* Get all key-value pairs */\n    public List<Pair> pairSet() {\n        List<Pair> pairSet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                pairSet.add(pair);\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    public List<Integer> keySet() {\n        List<Integer> keySet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                keySet.add(pair.key);\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    public List<String> valueSet() {\n        List<String> valueSet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                valueSet.add(pair.val);\n        }\n        return valueSet;\n    }\n\n    /* Print hash table */\n    public void print() {\n        for (Pair kv : pairSet()) {\n            System.out.println(kv.key + \" -> \" + kv.val);\n        }\n    }\n}\n
array_hash_map.cs
/* Key-value pair int->string */\nclass Pair(int key, string val) {\n    public int key = key;\n    public string val = val;\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    List<Pair?> buckets;\n    public ArrayHashMap() {\n        // Initialize array with 100 buckets\n        buckets = [];\n        for (int i = 0; i < 100; i++) {\n            buckets.Add(null);\n        }\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Query operation */\n    public string? Get(int key) {\n        int index = HashFunc(key);\n        Pair? pair = buckets[index];\n        if (pair == null) return null;\n        return pair.val;\n    }\n\n    /* Add operation */\n    public void Put(int key, string val) {\n        Pair pair = new(key, val);\n        int index = HashFunc(key);\n        buckets[index] = pair;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        int index = HashFunc(key);\n        // Set to null to represent deletion\n        buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    public List<Pair> PairSet() {\n        List<Pair> pairSet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                pairSet.Add(pair);\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    public List<int> KeySet() {\n        List<int> keySet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                keySet.Add(pair.key);\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    public List<string> ValueSet() {\n        List<string> valueSet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                valueSet.Add(pair.val);\n        }\n        return valueSet;\n    }\n\n    /* Print hash table */\n    public void Print() {\n        foreach (Pair kv in PairSet()) {\n            Console.WriteLine(kv.key + \" -> \" + kv.val);\n        }\n    }\n}\n
array_hash_map.go
/* Key-value pair */\ntype pair struct {\n    key int\n    val string\n}\n\n/* Hash table based on array implementation */\ntype arrayHashMap struct {\n    buckets []*pair\n}\n\n/* Initialize hash table */\nfunc newArrayHashMap() *arrayHashMap {\n    // Initialize array with 100 buckets\n    buckets := make([]*pair, 100)\n    return &arrayHashMap{buckets: buckets}\n}\n\n/* Hash function */\nfunc (a *arrayHashMap) hashFunc(key int) int {\n    index := key % 100\n    return index\n}\n\n/* Query operation */\nfunc (a *arrayHashMap) get(key int) string {\n    index := a.hashFunc(key)\n    pair := a.buckets[index]\n    if pair == nil {\n        return \"Not Found\"\n    }\n    return pair.val\n}\n\n/* Add operation */\nfunc (a *arrayHashMap) put(key int, val string) {\n    pair := &pair{key: key, val: val}\n    index := a.hashFunc(key)\n    a.buckets[index] = pair\n}\n\n/* Remove operation */\nfunc (a *arrayHashMap) remove(key int) {\n    index := a.hashFunc(key)\n    // Set to nil to delete\n    a.buckets[index] = nil\n}\n\n/* Get all key pairs */\nfunc (a *arrayHashMap) pairSet() []*pair {\n    var pairs []*pair\n    for _, pair := range a.buckets {\n        if pair != nil {\n            pairs = append(pairs, pair)\n        }\n    }\n    return pairs\n}\n\n/* Get all keys */\nfunc (a *arrayHashMap) keySet() []int {\n    var keys []int\n    for _, pair := range a.buckets {\n        if pair != nil {\n            keys = append(keys, pair.key)\n        }\n    }\n    return keys\n}\n\n/* Get all values */\nfunc (a *arrayHashMap) valueSet() []string {\n    var values []string\n    for _, pair := range a.buckets {\n        if pair != nil {\n            values = append(values, pair.val)\n        }\n    }\n    return values\n}\n\n/* Print hash table */\nfunc (a *arrayHashMap) print() {\n    for _, pair := range a.buckets {\n        if pair != nil {\n            fmt.Println(pair.key, \"->\", pair.val)\n        }\n    }\n}\n
array_hash_map.swift
/* Key-value pair */\nclass Pair: Equatable {\n    public var key: Int\n    public var val: String\n\n    public init(key: Int, val: String) {\n        self.key = key\n        self.val = val\n    }\n\n    public static func == (lhs: Pair, rhs: Pair) -> Bool {\n        lhs.key == rhs.key && lhs.val == rhs.val\n    }\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    private var buckets: [Pair?]\n\n    init() {\n        // Initialize array with 100 buckets\n        buckets = Array(repeating: nil, count: 100)\n    }\n\n    /* Hash function */\n    private func hashFunc(key: Int) -> Int {\n        let index = key % 100\n        return index\n    }\n\n    /* Query operation */\n    func get(key: Int) -> String? {\n        let index = hashFunc(key: key)\n        let pair = buckets[index]\n        return pair?.val\n    }\n\n    /* Add operation */\n    func put(key: Int, val: String) {\n        let pair = Pair(key: key, val: val)\n        let index = hashFunc(key: key)\n        buckets[index] = pair\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        let index = hashFunc(key: key)\n        // Set to nil to delete\n        buckets[index] = nil\n    }\n\n    /* Get all key-value pairs */\n    func pairSet() -> [Pair] {\n        buckets.compactMap { $0 }\n    }\n\n    /* Get all keys */\n    func keySet() -> [Int] {\n        buckets.compactMap { $0?.key }\n    }\n\n    /* Get all values */\n    func valueSet() -> [String] {\n        buckets.compactMap { $0?.val }\n    }\n\n    /* Print hash table */\n    func print() {\n        for pair in pairSet() {\n            Swift.print(\"\\(pair.key) -> \\(pair.val)\")\n        }\n    }\n}\n
array_hash_map.js
/* Key-value pair Number -> String */\nclass Pair {\n    constructor(key, val) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    #buckets;\n    constructor() {\n        // Initialize array with 100 buckets\n        this.#buckets = new Array(100).fill(null);\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % 100;\n    }\n\n    /* Query operation */\n    get(key) {\n        let index = this.#hashFunc(key);\n        let pair = this.#buckets[index];\n        if (pair === null) return null;\n        return pair.val;\n    }\n\n    /* Add operation */\n    set(key, val) {\n        let index = this.#hashFunc(key);\n        this.#buckets[index] = new Pair(key, val);\n    }\n\n    /* Remove operation */\n    delete(key) {\n        let index = this.#hashFunc(key);\n        // Set to null to represent deletion\n        this.#buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    entries() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i]);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all keys */\n    keys() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i].key);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all values */\n    values() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i].val);\n            }\n        }\n        return arr;\n    }\n\n    /* Print hash table */\n    print() {\n        let pairSet = this.entries();\n        for (const pair of pairSet) {\n            console.info(`${pair.key} -> ${pair.val}`);\n        }\n    }\n}\n
array_hash_map.ts
/* Key-value pair Number -> String */\nclass Pair {\n    public key: number;\n    public val: string;\n\n    constructor(key: number, val: string) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    private readonly buckets: (Pair | null)[];\n\n    constructor() {\n        // Initialize array with 100 buckets\n        this.buckets = new Array(100).fill(null);\n    }\n\n    /* Hash function */\n    private hashFunc(key: number): number {\n        return key % 100;\n    }\n\n    /* Query operation */\n    public get(key: number): string | null {\n        let index = this.hashFunc(key);\n        let pair = this.buckets[index];\n        if (pair === null) return null;\n        return pair.val;\n    }\n\n    /* Add operation */\n    public set(key: number, val: string) {\n        let index = this.hashFunc(key);\n        this.buckets[index] = new Pair(key, val);\n    }\n\n    /* Remove operation */\n    public delete(key: number) {\n        let index = this.hashFunc(key);\n        // Set to null to represent deletion\n        this.buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    public entries(): (Pair | null)[] {\n        let arr: (Pair | null)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i]);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all keys */\n    public keys(): (number | undefined)[] {\n        let arr: (number | undefined)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i].key);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all values */\n    public values(): (string | undefined)[] {\n        let arr: (string | undefined)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i].val);\n            }\n        }\n        return arr;\n    }\n\n    /* Print hash table */\n    public print() {\n        let pairSet = this.entries();\n        for (const pair of pairSet) {\n            console.info(`${pair.key} -> ${pair.val}`);\n        }\n    }\n}\n
array_hash_map.dart
/* Key-value pair */\nclass Pair {\n  int key;\n  String val;\n  Pair(this.key, this.val);\n}\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n  late List<Pair?> _buckets;\n\n  ArrayHashMap() {\n    // Initialize array with 100 buckets\n    _buckets = List.filled(100, null);\n  }\n\n  /* Hash function */\n  int _hashFunc(int key) {\n    final int index = key % 100;\n    return index;\n  }\n\n  /* Query operation */\n  String? get(int key) {\n    final int index = _hashFunc(key);\n    final Pair? pair = _buckets[index];\n    if (pair == null) {\n      return null;\n    }\n    return pair.val;\n  }\n\n  /* Add operation */\n  void put(int key, String val) {\n    final Pair pair = Pair(key, val);\n    final int index = _hashFunc(key);\n    _buckets[index] = pair;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    final int index = _hashFunc(key);\n    _buckets[index] = null;\n  }\n\n  /* Get all key-value pairs */\n  List<Pair> pairSet() {\n    List<Pair> pairSet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        pairSet.add(pair);\n      }\n    }\n    return pairSet;\n  }\n\n  /* Get all keys */\n  List<int> keySet() {\n    List<int> keySet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        keySet.add(pair.key);\n      }\n    }\n    return keySet;\n  }\n\n  /* Get all values */\n  List<String> values() {\n    List<String> valueSet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        valueSet.add(pair.val);\n      }\n    }\n    return valueSet;\n  }\n\n  /* Print hash table */\n  void printHashMap() {\n    for (final Pair kv in pairSet()) {\n      print(\"${kv.key} -> ${kv.val}\");\n    }\n  }\n}\n
array_hash_map.rs
/* Key-value pair */\n#[derive(Debug, Clone, PartialEq)]\npub struct Pair {\n    pub key: i32,\n    pub val: String,\n}\n\n/* Hash table based on array implementation */\npub struct ArrayHashMap {\n    buckets: Vec<Option<Pair>>,\n}\n\nimpl ArrayHashMap {\n    pub fn new() -> ArrayHashMap {\n        // Initialize array with 100 buckets\n        Self {\n            buckets: vec![None; 100],\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        key as usize % 100\n    }\n\n    /* Query operation */\n    pub fn get(&self, key: i32) -> Option<&String> {\n        let index = self.hash_func(key);\n        self.buckets[index].as_ref().map(|pair| &pair.val)\n    }\n\n    /* Add operation */\n    pub fn put(&mut self, key: i32, val: &str) {\n        let index = self.hash_func(key);\n        self.buckets[index] = Some(Pair {\n            key,\n            val: val.to_string(),\n        });\n    }\n\n    /* Remove operation */\n    pub fn remove(&mut self, key: i32) {\n        let index = self.hash_func(key);\n        // Set to None to represent removal\n        self.buckets[index] = None;\n    }\n\n    /* Get all key-value pairs */\n    pub fn entry_set(&self) -> Vec<&Pair> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref())\n            .collect()\n    }\n\n    /* Get all keys */\n    pub fn key_set(&self) -> Vec<&i32> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref().map(|pair| &pair.key))\n            .collect()\n    }\n\n    /* Get all values */\n    pub fn value_set(&self) -> Vec<&String> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref().map(|pair| &pair.val))\n            .collect()\n    }\n\n    /* Print hash table */\n    pub fn print(&self) {\n        for pair in self.entry_set() {\n            println!(\"{} -> {}\", pair.key, pair.val);\n        }\n    }\n}\n
array_hash_map.c
/* Key-value pair int->string */\ntypedef struct {\n    int key;\n    char *val;\n} Pair;\n\n/* Hash table based on array implementation */\ntypedef struct {\n    Pair *buckets[MAX_SIZE];\n} ArrayHashMap;\n\n/* Constructor */\nArrayHashMap *newArrayHashMap() {\n    ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap));\n    for (int i=0; i < MAX_SIZE; i++) {\n        hmap->buckets[i] = NULL;\n    }\n    return hmap;\n}\n\n/* Destructor */\nvoid delArrayHashMap(ArrayHashMap *hmap) {\n    for (int i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            free(hmap->buckets[i]->val);\n            free(hmap->buckets[i]);\n        }\n    }\n    free(hmap);\n}\n\n/* Add operation */\nvoid put(ArrayHashMap *hmap, const int key, const char *val) {\n    Pair *Pair = malloc(sizeof(Pair));\n    Pair->key = key;\n    Pair->val = malloc(strlen(val) + 1);\n    strcpy(Pair->val, val);\n\n    int index = hashFunc(key);\n    hmap->buckets[index] = Pair;\n}\n\n/* Remove operation */\nvoid removeItem(ArrayHashMap *hmap, const int key) {\n    int index = hashFunc(key);\n    free(hmap->buckets[index]->val);\n    free(hmap->buckets[index]);\n    hmap->buckets[index] = NULL;\n}\n\n/* Get all key-value pairs */\nvoid pairSet(ArrayHashMap *hmap, MapSet *set) {\n    Pair *entries;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count valid key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    entries = malloc(sizeof(Pair) * total);\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            entries[index].key = hmap->buckets[i]->key;\n            entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1);\n            strcpy(entries[index].val, hmap->buckets[i]->val);\n            index++;\n        }\n    }\n    set->set = entries;\n    set->len = total;\n}\n\n/* Get all keys */\nvoid keySet(ArrayHashMap *hmap, MapSet *set) {\n    int *keys;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count valid key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    keys = malloc(total * sizeof(int));\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            keys[index] = hmap->buckets[i]->key;\n            index++;\n        }\n    }\n    set->set = keys;\n    set->len = total;\n}\n\n/* Get all values */\nvoid valueSet(ArrayHashMap *hmap, MapSet *set) {\n    char **vals;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count valid key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    vals = malloc(total * sizeof(char *));\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            vals[index] = hmap->buckets[i]->val;\n            index++;\n        }\n    }\n    set->set = vals;\n    set->len = total;\n}\n\n/* Print hash table */\nvoid print(ArrayHashMap *hmap) {\n    int i;\n    MapSet set;\n    pairSet(hmap, &set);\n    Pair *entries = (Pair *)set.set;\n    for (i = 0; i < set.len; i++) {\n        printf(\"%d -> %s\\n\", entries[i].key, entries[i].val);\n    }\n    free(set.set);\n}\n
array_hash_map.kt
/* Key-value pair */\nclass Pair(\n    var key: Int,\n    var _val: String\n)\n\n/* Hash table based on array implementation */\nclass ArrayHashMap {\n    // Initialize array with 100 buckets\n    private val buckets = arrayOfNulls<Pair>(100)\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        val index = key % 100\n        return index\n    }\n\n    /* Query operation */\n    fun get(key: Int): String? {\n        val index = hashFunc(key)\n        val pair = buckets[index] ?: return null\n        return pair._val\n    }\n\n    /* Add operation */\n    fun put(key: Int, _val: String) {\n        val pair = Pair(key, _val)\n        val index = hashFunc(key)\n        buckets[index] = pair\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        val index = hashFunc(key)\n        // Set to null to represent deletion\n        buckets[index] = null\n    }\n\n    /* Get all key-value pairs */\n    fun pairSet(): MutableList<Pair> {\n        val pairSet = mutableListOf<Pair>()\n        for (pair in buckets) {\n            if (pair != null)\n                pairSet.add(pair)\n        }\n        return pairSet\n    }\n\n    /* Get all keys */\n    fun keySet(): MutableList<Int> {\n        val keySet = mutableListOf<Int>()\n        for (pair in buckets) {\n            if (pair != null)\n                keySet.add(pair.key)\n        }\n        return keySet\n    }\n\n    /* Get all values */\n    fun valueSet(): MutableList<String> {\n        val valueSet = mutableListOf<String>()\n        for (pair in buckets) {\n            if (pair != null)\n                valueSet.add(pair._val)\n        }\n        return valueSet\n    }\n\n    /* Print hash table */\n    fun print() {\n        for (kv in pairSet()) {\n            val key = kv.key\n            val _val = kv._val\n            println(\"$key -> $_val\")\n        }\n    }\n}\n
array_hash_map.rb
### Key-value pair ###\nclass Pair\n  attr_accessor :key, :val\n\n  def initialize(key, val)\n    @key = key\n    @val = val\n  end\nend\n\n### Hash map based on array ###\nclass ArrayHashMap\n  ### Constructor ###\n  def initialize\n    # Initialize array with 100 buckets\n    @buckets = Array.new(100)\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    index = key % 100\n  end\n\n  ### Query operation ###\n  def get(key)\n    index = hash_func(key)\n    pair = @buckets[index]\n\n    return if pair.nil?\n    pair.val\n  end\n\n  ### Add operation ###\n  def put(key, val)\n    pair = Pair.new(key, val)\n    index = hash_func(key)\n    @buckets[index] = pair\n  end\n\n  ### Delete operation ###\n  def remove(key)\n    index = hash_func(key)\n    # Set to nil to delete\n    @buckets[index] = nil\n  end\n\n  ### Get all key-value pairs ###\n  def entry_set\n    result = []\n    @buckets.each { |pair| result << pair unless pair.nil? }\n    result\n  end\n\n  ### Get all keys ###\n  def key_set\n    result = []\n    @buckets.each { |pair| result << pair.key unless pair.nil? }\n    result\n  end\n\n  ### Get all values ###\n  def value_set\n    result = []\n    @buckets.each { |pair| result << pair.val unless pair.nil? }\n    result\n  end\n\n  ### Print hash table ###\n  def print\n    @buckets.each { |pair| puts \"#{pair.key} -> #{pair.val}\" unless pair.nil? }\n  end\nend\n
","path":["Chapter 6. Hashing","6.1   Hash Table"],"tags":[]},{"location":"chapter_hashing/hash_map/#613-hash-collision-and-resizing","level":2,"title":"6.1.3   Hash Collision and Resizing","text":"

Fundamentally, the role of a hash function is to map the input space consisting of all keys to the output space consisting of all array indices, and the input space is often much larger than the output space. Therefore, theoretically there must be cases where \"multiple inputs correspond to the same output\".

For the hash function in the above example, when the input keys have the same last two digits, the hash function produces the same output. For example, when querying two students with IDs 12836 and 20336, we get:

12836 % 100 = 36\n20336 % 100 = 36\n

As shown in Figure 6-3, two student IDs point to the same name, which is obviously incorrect. We call this situation where multiple inputs correspond to the same output a hash collision.

Figure 6-3   Hash collision example

It's easy to see that the larger the hash table capacity \\(n\\), the lower the probability that multiple keys will be assigned to the same bucket, and the fewer collisions. Therefore, we can reduce hash collisions by expanding the hash table.

As shown in Figure 6-4, before expansion, the key-value pairs (136, A) and (236, D) collided, but after expansion, the collision disappears.

Figure 6-4   Hash table resizing

Similar to array expansion, hash table expansion requires migrating all key-value pairs from the original hash table to the new hash table, which is very time-consuming. Moreover, since the hash table capacity capacity changes, we need to recalculate the storage locations of all key-value pairs through the hash function, further increasing the computational overhead of the expansion process. For this reason, programming languages typically reserve a sufficiently large hash table capacity to prevent frequent expansion.

The load factor is an important concept for hash tables. It is defined as the number of elements in the hash table divided by the number of buckets, and is used to measure the severity of hash collisions. It is also commonly used as a trigger condition for hash table expansion. For example, in Java, when the load factor exceeds \\(0.75\\), the system will expand the hash table to \\(2\\) times its original size.

","path":["Chapter 6. Hashing","6.1   Hash Table"],"tags":[]},{"location":"chapter_hashing/summary/","level":1,"title":"6.4   Summary","text":"","path":["Chapter 6. Hashing","6.4   Summary"],"tags":[]},{"location":"chapter_hashing/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Given an input key, a hash table can retrieve the corresponding value in \\(O(1)\\) time, which is highly efficient.
  • Common hash table operations include querying, adding key-value pairs, deleting key-value pairs, and traversing the hash table.
  • The hash function maps a key to an array index, allowing access to the corresponding bucket and retrieval of the value.
  • Two different keys may end up with the same array index after hashing, leading to erroneous query results. This phenomenon is known as hash collision.
  • The larger the capacity of the hash table, the lower the probability of hash collisions. Therefore, hash table expansion can mitigate hash collisions. Similar to array expansion, hash table expansion is costly.
  • The load factor, defined as the number of elements divided by the number of buckets, reflects the severity of hash collisions and is often used as a condition to trigger hash table expansion.
  • Separate chaining addresses hash collisions by converting each element into a linked list, storing all colliding elements in the same linked list. However, excessively long linked lists can reduce query efficiency, which can be improved by converting the linked lists into red-black trees.
  • Open addressing handles hash collisions through multiple probing. Linear probing uses a fixed step size but cannot delete elements and is prone to clustering. Double hashing uses multiple hash functions for probing, which reduces clustering compared to linear probing but increases computational overhead.
  • Different programming languages adopt various hash table implementations. For example, Java's HashMap uses separate chaining, while Python's dict employs open addressing.
  • In hash tables, we desire hash algorithms with determinism, high efficiency, and uniform distribution. In cryptography, hash algorithms should also possess collision resistance and the avalanche effect.
  • Hash algorithms typically use large prime numbers as moduli to maximize the uniform distribution of hash values and reduce hash collisions.
  • Common hash algorithms include MD5, SHA-1, SHA-2, and SHA-3. MD5 is often used for file integrity checks, while SHA-2 is commonly used in secure applications and protocols.
  • Programming languages usually provide built-in hash algorithms for data types to calculate bucket indices in hash tables. Generally, only immutable objects are hashable.
","path":["Chapter 6. Hashing","6.4   Summary"],"tags":[]},{"location":"chapter_hashing/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: When does the time complexity of a hash table degrade to \\(O(n)\\)?

The time complexity of a hash table can degrade to \\(O(n)\\) when hash collisions are severe. When the hash function is well-designed, the capacity is set appropriately, and collisions are evenly distributed, the time complexity is \\(O(1)\\). We usually consider the time complexity to be \\(O(1)\\) when using built-in hash tables in programming languages.

Q: Why not use the hash function \\(f(x) = x\\)? This would eliminate collisions.

Under the hash function \\(f(x) = x\\), each element corresponds to a unique bucket index, which is equivalent to an array. However, the input space is usually much larger than the output space (array length), so the last step of a hash function is often to take the modulo of the array length. In other words, the goal of a hash table is to map a larger state space to a smaller one while providing \\(O(1)\\) query efficiency.

Q: Why can hash tables be more efficient than arrays, linked lists, or binary trees, even though hash tables are implemented using these structures?

Firstly, hash tables have higher time efficiency but lower space efficiency. A significant portion of memory in hash tables remains unused.

Secondly, hash tables are only more time-efficient in specific use cases. If a feature can be implemented with the same time complexity using an array or a linked list, it's usually faster than using a hash table. This is because the computation of the hash function incurs overhead, making the constant factor in the time complexity larger.

Lastly, the time complexity of hash tables can degrade. For example, in separate chaining, we perform search operations in a linked list or red-black tree, which still risks degrading to \\(O(n)\\) time.

Q: Does double hashing also have the flaw of not being able to delete elements directly? Can space marked as deleted be reused?

Double hashing is a form of open addressing, and all open addressing methods have the drawback of not being able to delete elements directly; they require marking elements as deleted. Marked spaces can be reused. When inserting new elements into the hash table, and the hash function points to a position marked as deleted, that position can be used by the new element. This maintains the probing sequence of the hash table while ensuring efficient use of space.

Q: Why do hash collisions occur during the search process in linear probing?

During the search process, the hash function points to the corresponding bucket and key-value pair. If the key doesn't match, it indicates a hash collision. Therefore, linear probing will search downward at a predetermined step size until the correct key-value pair is found or the search fails.

Q: Why can expanding a hash table alleviate hash collisions?

The last step of a hash function often involves taking the modulo of the array length \\(n\\), to keep the output within the array index range. When expanding, the array length \\(n\\) changes, and the indices corresponding to the keys may also change. Keys that were previously mapped to the same bucket might be distributed across multiple buckets after expansion, thereby mitigating hash collisions.

","path":["Chapter 6. Hashing","6.4   Summary"],"tags":[]},{"location":"chapter_heap/","level":1,"title":"Chapter 8.   Heap","text":"

Abstract

Heaps are like mountain peaks, layered and undulating, each with its unique form.

The peaks rise and fall at varying heights, yet the tallest peak always catches the eye first.

","path":["Chapter 8. Heap","Chapter 8.   Heap"],"tags":[]},{"location":"chapter_heap/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 8.1   Heap
  • 8.2   Building a Heap
  • 8.3   Top-K Problem
  • 8.4   Summary
","path":["Chapter 8. Heap","Chapter 8.   Heap"],"tags":[]},{"location":"chapter_heap/build_heap/","level":1,"title":"8.2   Heap Construction Operation","text":"

In some cases, we want to build a heap using all elements of a list, and this process is called \"heap construction operation.\"

","path":["Chapter 8. Heap","8.2   Heap Construction Operation"],"tags":[]},{"location":"chapter_heap/build_heap/#821-implementing-with-element-insertion","level":2,"title":"8.2.1   Implementing with Element Insertion","text":"

We first create an empty heap, then iterate through the list, performing the \"element insertion operation\" on each element in sequence. This means adding the element to the bottom of the heap and then performing \"bottom-to-top\" heapify on that element.

Each time an element is inserted into the heap, the heap's length increases by one. Since nodes are added to the binary tree sequentially from top to bottom, the heap is constructed \"from top to bottom.\"

Given \\(n\\) elements, each element's insertion operation takes \\(O(\\log{n})\\) time, so the time complexity of this heap construction method is \\(O(n \\log n)\\).

","path":["Chapter 8. Heap","8.2   Heap Construction Operation"],"tags":[]},{"location":"chapter_heap/build_heap/#822-implementing-through-heapify-traversal","level":2,"title":"8.2.2   Implementing Through Heapify Traversal","text":"

In fact, we can implement a more efficient heap construction method in two steps.

  1. Add all elements of the list as-is to the heap, at which point the heap property is not yet satisfied.
  2. Traverse the heap in reverse order (reverse of level-order traversal), performing \"top-to-bottom heapify\" on each non-leaf node in sequence.

After heapifying a node, the subtree rooted at that node becomes a valid sub-heap. Since we traverse in reverse order, the heap is constructed \"from bottom to top.\"

The reason for choosing reverse order traversal is that it ensures the subtree below the current node is already a valid sub-heap, making the heapification of the current node effective.

It's worth noting that since leaf nodes have no children, they are naturally valid sub-heaps and do not require heapification. As shown in the code below, the last non-leaf node is the parent of the last node; we start from it and traverse in reverse order to perform heapification:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
def __init__(self, nums: list[int]):\n    \"\"\"Constructor, build heap based on input list\"\"\"\n    # Add list elements to heap as is\n    self.max_heap = nums\n    # Heapify all nodes except leaf nodes\n    for i in range(self.parent(self.size() - 1), -1, -1):\n        self.sift_down(i)\n
my_heap.cpp
/* Constructor, build heap based on input list */\nMaxHeap(vector<int> nums) {\n    // Add list elements to heap as is\n    maxHeap = nums;\n    // Heapify all nodes except leaf nodes\n    for (int i = parent(size() - 1); i >= 0; i--) {\n        siftDown(i);\n    }\n}\n
my_heap.java
/* Constructor, build heap based on input list */\nMaxHeap(List<Integer> nums) {\n    // Add list elements to heap as is\n    maxHeap = new ArrayList<>(nums);\n    // Heapify all nodes except leaf nodes\n    for (int i = parent(size() - 1); i >= 0; i--) {\n        siftDown(i);\n    }\n}\n
my_heap.cs
/* Constructor, build heap from input list */\nMaxHeap(IEnumerable<int> nums) {\n    // Add list elements to heap as is\n    maxHeap = new List<int>(nums);\n    // Heapify all nodes except leaf nodes\n    var size = Parent(this.Size() - 1);\n    for (int i = size; i >= 0; i--) {\n        SiftDown(i);\n    }\n}\n
my_heap.go
/* Constructor, build heap from slice */\nfunc newMaxHeap(nums []any) *maxHeap {\n    // Add list elements to heap as is\n    h := &maxHeap{data: nums}\n    for i := h.parent(len(h.data) - 1); i >= 0; i-- {\n        // Heapify all nodes except leaf nodes\n        h.siftDown(i)\n    }\n    return h\n}\n
my_heap.swift
/* Constructor, build heap based on input list */\ninit(nums: [Int]) {\n    // Add list elements to heap as is\n    maxHeap = nums\n    // Heapify all nodes except leaf nodes\n    for i in (0 ... parent(i: size() - 1)).reversed() {\n        siftDown(i: i)\n    }\n}\n
my_heap.js
/* Constructor, build empty heap or build heap from input list */\nconstructor(nums) {\n    // Add list elements to heap as is\n    this.#maxHeap = nums === undefined ? [] : [...nums];\n    // Heapify all nodes except leaf nodes\n    for (let i = this.#parent(this.size() - 1); i >= 0; i--) {\n        this.#siftDown(i);\n    }\n}\n
my_heap.ts
/* Constructor, build empty heap or build heap from input list */\nconstructor(nums?: number[]) {\n    // Add list elements to heap as is\n    this.maxHeap = nums === undefined ? [] : [...nums];\n    // Heapify all nodes except leaf nodes\n    for (let i = this.parent(this.size() - 1); i >= 0; i--) {\n        this.siftDown(i);\n    }\n}\n
my_heap.dart
/* Constructor, build heap based on input list */\nMaxHeap(List<int> nums) {\n  // Add list elements to heap as is\n  _maxHeap = nums;\n  // Heapify all nodes except leaf nodes\n  for (int i = _parent(size() - 1); i >= 0; i--) {\n    siftDown(i);\n  }\n}\n
my_heap.rs
/* Constructor, build heap based on input list */\nfn new(nums: Vec<i32>) -> Self {\n    // Add list elements to heap as is\n    let mut heap = MaxHeap { max_heap: nums };\n    // Heapify all nodes except leaf nodes\n    for i in (0..=Self::parent(heap.size() - 1)).rev() {\n        heap.sift_down(i);\n    }\n    heap\n}\n
my_heap.c
/* Constructor, build heap from slice */\nMaxHeap *newMaxHeap(int nums[], int size) {\n    // Push all elements to heap\n    MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap));\n    maxHeap->size = size;\n    memcpy(maxHeap->data, nums, size * sizeof(int));\n    for (int i = parent(maxHeap, size - 1); i >= 0; i--) {\n        // Heapify all nodes except leaf nodes\n        siftDown(maxHeap, i);\n    }\n    return maxHeap;\n}\n
my_heap.kt
/* Max heap */\nclass MaxHeap(nums: MutableList<Int>?) {\n    // Use list instead of array, no need to consider capacity expansion\n    private val maxHeap = mutableListOf<Int>()\n\n    /* Constructor, build heap based on input list */\n    init {\n        // Add list elements to heap as is\n        maxHeap.addAll(nums!!)\n        // Heapify all nodes except leaf nodes\n        for (i in parent(size() - 1) downTo 0) {\n            siftDown(i)\n        }\n    }\n\n    /* Get index of left child node */\n    private fun left(i: Int): Int {\n        return 2 * i + 1\n    }\n\n    /* Get index of right child node */\n    private fun right(i: Int): Int {\n        return 2 * i + 2\n    }\n\n    /* Get index of parent node */\n    private fun parent(i: Int): Int {\n        return (i - 1) / 2 // Floor division\n    }\n\n    /* Swap elements */\n    private fun swap(i: Int, j: Int) {\n        val temp = maxHeap[i]\n        maxHeap[i] = maxHeap[j]\n        maxHeap[j] = temp\n    }\n\n    /* Get heap size */\n    fun size(): Int {\n        return maxHeap.size\n    }\n\n    /* Check if heap is empty */\n    fun isEmpty(): Boolean {\n        /* Check if heap is empty */\n        return size() == 0\n    }\n\n    /* Access top element */\n    fun peek(): Int {\n        return maxHeap[0]\n    }\n\n    /* Element enters heap */\n    fun push(_val: Int) {\n        // Add node\n        maxHeap.add(_val)\n        // Heapify from bottom to top\n        siftUp(size() - 1)\n    }\n\n    /* Starting from node i, heapify from bottom to top */\n    private fun siftUp(it: Int) {\n        // Kotlin function parameters are immutable, so create temporary variable\n        var i = it\n        while (true) {\n            // Get parent node of node i\n            val p = parent(i)\n            // When \"crossing root node\" or \"node needs no repair\", end heapify\n            if (p < 0 || maxHeap[i] <= maxHeap[p]) break\n            // Swap two nodes\n            swap(i, p)\n            // Loop upward heapify\n            i = p\n        }\n    }\n\n    /* Element exits heap */\n    fun pop(): Int {\n        // Handle empty case\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        // Delete node\n        swap(0, size() - 1)\n        // Remove node\n        val _val = maxHeap.removeAt(size() - 1)\n        // Return top element\n        siftDown(0)\n        // Return heap top element\n        return _val\n    }\n\n    /* Starting from node i, heapify from top to bottom */\n    private fun siftDown(it: Int) {\n        // Kotlin function parameters are immutable, so create temporary variable\n        var i = it\n        while (true) {\n            // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n            val l = left(i)\n            val r = right(i)\n            var ma = i\n            if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l\n            if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r\n            // Swap two nodes\n            if (ma == i) break\n            // Swap two nodes\n            swap(i, ma)\n            // Loop downwards heapification\n            i = ma\n        }\n    }\n\n    /* Driver Code */\n    fun print() {\n        val queue = PriorityQueue { a: Int, b: Int -> b - a }\n        queue.addAll(maxHeap)\n        printHeap(queue)\n    }\n}\n
my_heap.rb
### Constructor, build heap from input list ###\ndef initialize(nums)\n  # Add list elements to heap as is\n  @max_heap = nums\n  # Heapify all nodes except leaf nodes\n  parent(size - 1).downto(0) do |i|\n    sift_down(i)\n  end\nend\n
","path":["Chapter 8. Heap","8.2   Heap Construction Operation"],"tags":[]},{"location":"chapter_heap/build_heap/#823-complexity-analysis","level":2,"title":"8.2.3   Complexity Analysis","text":"

Next, let's attempt to derive the time complexity of this second heap construction method.

  • Assuming the complete binary tree has \\(n\\) nodes, then the number of leaf nodes is \\((n + 1) / 2\\), where \\(/\\) is floor division. Therefore, the number of nodes that need heapification is \\((n - 1) / 2\\).
  • In the top-to-bottom heapify process, each node is heapified at most to the leaf nodes, so the maximum number of iterations is the binary tree height \\(\\log n\\).

Multiplying these two together, we get a time complexity of \\(O(n \\log n)\\) for the heap construction process. However, this estimate is not accurate because it doesn't account for the property that binary trees have far more nodes at lower levels than at upper levels.

Let's perform a more accurate calculation. To reduce calculation difficulty, assume a \"perfect binary tree\" with \\(n\\) nodes and height \\(h\\); this assumption does not affect the correctness of the result.

Figure 8-5   Node count at each level of a perfect binary tree

As shown in Figure 8-5, the maximum number of iterations for a node's \"top-to-bottom heapify\" equals the distance from that node to the leaf nodes, which is precisely the \"node height.\" Therefore, we can sum the \"number of nodes \\(\\times\\) node height\" at each level to obtain the total number of heapify iterations for all nodes.

\\[ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \\dots + 2^{(h-1)}\\times1 \\]

To simplify the above expression, we need to use sequence knowledge from high school. First, multiply \\(T(h)\\) by \\(2\\) to get:

\\[ \\begin{aligned} T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \\dots + 2^{h-1}\\times1 \\newline 2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \\dots + 2^{h}\\times1 \\newline \\end{aligned} \\]

Using the method of differences, subtract the first equation \\(T(h)\\) from the second equation \\(2 T(h)\\) to get:

\\[ 2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \\dots + 2^{h-1} + 2^h \\]

Observing the above expression, we find that \\(T(h)\\) is a geometric series, which can be calculated directly using the sum formula, yielding a time complexity of:

\\[ \\begin{aligned} T(h) & = 2 \\frac{1 - 2^h}{1 - 2} - h \\newline & = 2^{h+1} - h - 2 \\newline & = O(2^h) \\end{aligned} \\]

Furthermore, a perfect binary tree with height \\(h\\) has \\(n = 2^{h+1} - 1\\) nodes, so the complexity is \\(O(2^h) = O(n)\\). This derivation shows that the time complexity of building a heap from an input list is \\(O(n)\\), which is highly efficient.

","path":["Chapter 8. Heap","8.2   Heap Construction Operation"],"tags":[]},{"location":"chapter_heap/heap/","level":1,"title":"8.1   Heap","text":"

A heap is a complete binary tree that satisfies specific conditions and can be mainly categorized into two types, as shown in Figure 8-1.

  • min heap: The value of any node \\(\\leq\\) the values of its child nodes.
  • max heap: The value of any node \\(\\geq\\) the values of its child nodes.

Figure 8-1   Min heap and max heap

As a special case of a complete binary tree, heaps have the following characteristics.

  • The bottom layer nodes are filled from left to right, and nodes in other layers are fully filled.
  • We call the root node of the binary tree the \"heap top\" and the bottom-rightmost node the \"heap bottom.\"
  • For max heaps (min heaps), the value of the heap top element (root node) is the largest (smallest).
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#811-common-heap-operations","level":2,"title":"8.1.1   Common Heap Operations","text":"

It should be noted that many programming languages provide a priority queue, which is an abstract data structure defined as a queue with priority sorting.

In fact, heaps are typically used to implement priority queues, with max heaps corresponding to priority queues where elements are dequeued in descending order. From a usage perspective, we can regard \"priority queue\" and \"heap\" as equivalent data structures. Therefore, this book does not make a special distinction between the two and uniformly refers to them as \"heap.\"

Common heap operations are shown in Table 8-1, and method names need to be determined based on the programming language.

Table 8-1   Efficiency of Heap Operations

Method name Description Time complexity push() Insert an element into the heap \\(O(\\log n)\\) pop() Remove the heap top element \\(O(\\log n)\\) peek() Access the heap top element (max/min value for max/min heap) \\(O(1)\\) size() Get the number of elements in the heap \\(O(1)\\) isEmpty() Check if the heap is empty \\(O(1)\\)

In practical applications, we can directly use the heap class (or priority queue class) provided by programming languages.

Similar to \"ascending order\" and \"descending order\" in sorting algorithms, we can implement conversion between \"min heap\" and \"max heap\" by setting a flag or modifying the Comparator. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby heap.py
# Initialize a min heap\nmin_heap, flag = [], 1\n# Initialize a max heap\nmax_heap, flag = [], -1\n\n# Python's heapq module implements a min heap by default\n# Consider negating elements before pushing them to the heap, which inverts the size relationship and thus implements a max heap\n# In this example, flag = 1 corresponds to a min heap, flag = -1 corresponds to a max heap\n\n# Push elements into the heap\nheapq.heappush(max_heap, flag * 1)\nheapq.heappush(max_heap, flag * 3)\nheapq.heappush(max_heap, flag * 2)\nheapq.heappush(max_heap, flag * 5)\nheapq.heappush(max_heap, flag * 4)\n\n# Get the heap top element\npeek: int = flag * max_heap[0] # 5\n\n# Remove the heap top element\n# The removed elements will form a descending sequence\nval = flag * heapq.heappop(max_heap) # 5\nval = flag * heapq.heappop(max_heap) # 4\nval = flag * heapq.heappop(max_heap) # 3\nval = flag * heapq.heappop(max_heap) # 2\nval = flag * heapq.heappop(max_heap) # 1\n\n# Get the heap size\nsize: int = len(max_heap)\n\n# Check if the heap is empty\nis_empty: bool = not max_heap\n\n# Build a heap from an input list\nmin_heap: list[int] = [1, 3, 2, 5, 4]\nheapq.heapify(min_heap)\n
heap.cpp
/* Initialize a heap */\n// Initialize a min heap\npriority_queue<int, vector<int>, greater<int>> minHeap;\n// Initialize a max heap\npriority_queue<int, vector<int>, less<int>> maxHeap;\n\n/* Push elements into the heap */\nmaxHeap.push(1);\nmaxHeap.push(3);\nmaxHeap.push(2);\nmaxHeap.push(5);\nmaxHeap.push(4);\n\n/* Get the heap top element */\nint peek = maxHeap.top(); // 5\n\n/* Remove the heap top element */\n// The removed elements will form a descending sequence\nmaxHeap.pop(); // 5\nmaxHeap.pop(); // 4\nmaxHeap.pop(); // 3\nmaxHeap.pop(); // 2\nmaxHeap.pop(); // 1\n\n/* Get the heap size */\nint size = maxHeap.size();\n\n/* Check if the heap is empty */\nbool isEmpty = maxHeap.empty();\n\n/* Build a heap from an input list */\nvector<int> input{1, 3, 2, 5, 4};\npriority_queue<int, vector<int>, greater<int>> minHeap(input.begin(), input.end());\n
heap.java
/* Initialize a heap */\n// Initialize a min heap\nQueue<Integer> minHeap = new PriorityQueue<>();\n// Initialize a max heap (use lambda expression to modify Comparator)\nQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);\n\n/* Push elements into the heap */\nmaxHeap.offer(1);\nmaxHeap.offer(3);\nmaxHeap.offer(2);\nmaxHeap.offer(5);\nmaxHeap.offer(4);\n\n/* Get the heap top element */\nint peek = maxHeap.peek(); // 5\n\n/* Remove the heap top element */\n// The removed elements will form a descending sequence\npeek = maxHeap.poll(); // 5\npeek = maxHeap.poll(); // 4\npeek = maxHeap.poll(); // 3\npeek = maxHeap.poll(); // 2\npeek = maxHeap.poll(); // 1\n\n/* Get the heap size */\nint size = maxHeap.size();\n\n/* Check if the heap is empty */\nboolean isEmpty = maxHeap.isEmpty();\n\n/* Build a heap from an input list */\nminHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4));\n
heap.cs
/* Initialize a heap */\n// Initialize a min heap\nPriorityQueue<int, int> minHeap = new();\n// Initialize a max heap (use lambda expression to modify Comparer)\nPriorityQueue<int, int> maxHeap = new(Comparer<int>.Create((x, y) => y.CompareTo(x)));\n\n/* Push elements into the heap */\nmaxHeap.Enqueue(1, 1);\nmaxHeap.Enqueue(3, 3);\nmaxHeap.Enqueue(2, 2);\nmaxHeap.Enqueue(5, 5);\nmaxHeap.Enqueue(4, 4);\n\n/* Get the heap top element */\nint peek = maxHeap.Peek();//5\n\n/* Remove the heap top element */\n// The removed elements will form a descending sequence\npeek = maxHeap.Dequeue();  // 5\npeek = maxHeap.Dequeue();  // 4\npeek = maxHeap.Dequeue();  // 3\npeek = maxHeap.Dequeue();  // 2\npeek = maxHeap.Dequeue();  // 1\n\n/* Get the heap size */\nint size = maxHeap.Count;\n\n/* Check if the heap is empty */\nbool isEmpty = maxHeap.Count == 0;\n\n/* Build a heap from an input list */\nminHeap = new PriorityQueue<int, int>([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]);\n
heap.go
// In Go, we can construct a max heap of integers by implementing heap.Interface\n// Implementing heap.Interface also requires implementing sort.Interface\ntype intHeap []any\n\n// Push implements the heap.Interface method for pushing an element into the heap\nfunc (h *intHeap) Push(x any) {\n    // Push and Pop use pointer receiver as parameters\n    // because they not only adjust the slice contents but also modify the slice length\n    *h = append(*h, x.(int))\n}\n\n// Pop implements the heap.Interface method for popping the heap top element\nfunc (h *intHeap) Pop() any {\n    // The element to be removed is stored at the end\n    last := (*h)[len(*h)-1]\n    *h = (*h)[:len(*h)-1]\n    return last\n}\n\n// Len is a sort.Interface method\nfunc (h *intHeap) Len() int {\n    return len(*h)\n}\n\n// Less is a sort.Interface method\nfunc (h *intHeap) Less(i, j int) bool {\n    // To implement a min heap, change this to a less-than sign\n    return (*h)[i].(int) > (*h)[j].(int)\n}\n\n// Swap is a sort.Interface method\nfunc (h *intHeap) Swap(i, j int) {\n    (*h)[i], (*h)[j] = (*h)[j], (*h)[i]\n}\n\n// Top gets the heap top element\nfunc (h *intHeap) Top() any {\n    return (*h)[0]\n}\n\n/* Driver Code */\nfunc TestHeap(t *testing.T) {\n    /* Initialize a heap */\n    // Initialize a max heap\n    maxHeap := &intHeap{}\n    heap.Init(maxHeap)\n    /* Push elements into the heap */\n    // Call heap.Interface methods to add elements\n    heap.Push(maxHeap, 1)\n    heap.Push(maxHeap, 3)\n    heap.Push(maxHeap, 2)\n    heap.Push(maxHeap, 4)\n    heap.Push(maxHeap, 5)\n\n    /* Get the heap top element */\n    top := maxHeap.Top()\n    fmt.Printf(\"Heap top element is %d\\n\", top)\n\n    /* Remove the heap top element */\n    // Call heap.Interface methods to remove elements\n    heap.Pop(maxHeap) // 5\n    heap.Pop(maxHeap) // 4\n    heap.Pop(maxHeap) // 3\n    heap.Pop(maxHeap) // 2\n    heap.Pop(maxHeap) // 1\n\n    /* Get the heap size */\n    size := len(*maxHeap)\n    fmt.Printf(\"Number of heap elements is %d\\n\", size)\n\n    /* Check if the heap is empty */\n    isEmpty := len(*maxHeap) == 0\n    fmt.Printf(\"Is the heap empty? %t\\n\", isEmpty)\n}\n
heap.swift
/* Initialize a heap */\n// Swift's Heap type supports both max heaps and min heaps, and requires importing swift-collections\nvar heap = Heap<Int>()\n\n/* Push elements into the heap */\nheap.insert(1)\nheap.insert(3)\nheap.insert(2)\nheap.insert(5)\nheap.insert(4)\n\n/* Get the heap top element */\nvar peek = heap.max()!\n\n/* Remove the heap top element */\npeek = heap.removeMax() // 5\npeek = heap.removeMax() // 4\npeek = heap.removeMax() // 3\npeek = heap.removeMax() // 2\npeek = heap.removeMax() // 1\n\n/* Get the heap size */\nlet size = heap.count\n\n/* Check if the heap is empty */\nlet isEmpty = heap.isEmpty\n\n/* Build a heap from an input list */\nlet heap2 = Heap([1, 3, 2, 5, 4])\n
heap.js
// JavaScript does not provide a built-in Heap class\n
heap.ts
// TypeScript does not provide a built-in Heap class\n
heap.dart
// Dart does not provide a built-in Heap class\n
heap.rs
use std::collections::BinaryHeap;\nuse std::cmp::Reverse;\n\n/* Initialize a heap */\n// Initialize a min heap\nlet mut min_heap = BinaryHeap::<Reverse<i32>>::new();\n// Initialize a max heap\nlet mut max_heap = BinaryHeap::new();\n\n/* Push elements into the heap */\nmax_heap.push(1);\nmax_heap.push(3);\nmax_heap.push(2);\nmax_heap.push(5);\nmax_heap.push(4);\n\n/* Get the heap top element */\nlet peek = max_heap.peek().unwrap();  // 5\n\n/* Remove the heap top element */\n// The removed elements will form a descending sequence\nlet peek = max_heap.pop().unwrap();   // 5\nlet peek = max_heap.pop().unwrap();   // 4\nlet peek = max_heap.pop().unwrap();   // 3\nlet peek = max_heap.pop().unwrap();   // 2\nlet peek = max_heap.pop().unwrap();   // 1\n\n/* Get the heap size */\nlet size = max_heap.len();\n\n/* Check if the heap is empty */\nlet is_empty = max_heap.is_empty();\n\n/* Build a heap from an input list */\nlet min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]);\n
heap.c
// C does not provide a built-in Heap class\n
heap.kt
/* Initialize a heap */\n// Initialize a min heap\nvar minHeap = PriorityQueue<Int>()\n// Initialize a max heap (use lambda expression to modify Comparator)\nval maxHeap = PriorityQueue { a: Int, b: Int -> b - a }\n\n/* Push elements into the heap */\nmaxHeap.offer(1)\nmaxHeap.offer(3)\nmaxHeap.offer(2)\nmaxHeap.offer(5)\nmaxHeap.offer(4)\n\n/* Get the heap top element */\nvar peek = maxHeap.peek() // 5\n\n/* Remove the heap top element */\n// The removed elements will form a descending sequence\npeek = maxHeap.poll() // 5\npeek = maxHeap.poll() // 4\npeek = maxHeap.poll() // 3\npeek = maxHeap.poll() // 2\npeek = maxHeap.poll() // 1\n\n/* Get the heap size */\nval size = maxHeap.size\n\n/* Check if the heap is empty */\nval isEmpty = maxHeap.isEmpty()\n\n/* Build a heap from an input list */\nminHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4))\n
heap.rb
# Ruby does not provide a built-in Heap class\n
Code Visualization

Full Screen >

","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#812-implementation-of-the-heap","level":2,"title":"8.1.2   Implementation of the Heap","text":"

The following implementation is of a max heap. To convert it to a min heap, simply invert all size logic comparisons (for example, replace \\(\\geq\\) with \\(\\leq\\)). Interested readers are encouraged to implement this on their own.

","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#1-heap-storage-and-representation","level":3,"title":"1.   Heap Storage and Representation","text":"

As mentioned in the \"Binary Tree\" chapter, complete binary trees are well-suited for array representation. Since heaps are a type of complete binary tree, we will use arrays to store heaps.

When representing a binary tree with an array, elements represent node values, and indexes represent node positions in the binary tree. Node pointers are implemented through index mapping formulas.

As shown in Figure 8-2, given an index \\(i\\), the index of its left child is \\(2i + 1\\), the index of its right child is \\(2i + 2\\), and the index of its parent is \\((i - 1) / 2\\) (floor division). When an index is out of bounds, it indicates a null node or that the node does not exist.

Figure 8-2   Representation and storage of heaps

We can encapsulate the index mapping formula into functions for convenient subsequent use:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
def left(self, i: int) -> int:\n    \"\"\"Get index of left child node\"\"\"\n    return 2 * i + 1\n\ndef right(self, i: int) -> int:\n    \"\"\"Get index of right child node\"\"\"\n    return 2 * i + 2\n\ndef parent(self, i: int) -> int:\n    \"\"\"Get index of parent node\"\"\"\n    return (i - 1) // 2  # Floor division\n
my_heap.cpp
/* Get index of left child node */\nint left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\nint right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\nint parent(int i) {\n    return (i - 1) / 2; // Floor division\n}\n
my_heap.java
/* Get index of left child node */\nint left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\nint right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\nint parent(int i) {\n    return (i - 1) / 2; // Floor division\n}\n
my_heap.cs
/* Get index of left child node */\nint Left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\nint Right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\nint Parent(int i) {\n    return (i - 1) / 2; // Floor division\n}\n
my_heap.go
/* Get index of left child node */\nfunc (h *maxHeap) left(i int) int {\n    return 2*i + 1\n}\n\n/* Get index of right child node */\nfunc (h *maxHeap) right(i int) int {\n    return 2*i + 2\n}\n\n/* Get index of parent node */\nfunc (h *maxHeap) parent(i int) int {\n    // Floor division\n    return (i - 1) / 2\n}\n
my_heap.swift
/* Get index of left child node */\nfunc left(i: Int) -> Int {\n    2 * i + 1\n}\n\n/* Get index of right child node */\nfunc right(i: Int) -> Int {\n    2 * i + 2\n}\n\n/* Get index of parent node */\nfunc parent(i: Int) -> Int {\n    (i - 1) / 2 // Floor division\n}\n
my_heap.js
/* Get index of left child node */\n#left(i) {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\n#right(i) {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\n#parent(i) {\n    return Math.floor((i - 1) / 2); // Floor division\n}\n
my_heap.ts
/* Get index of left child node */\nleft(i: number): number {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\nright(i: number): number {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\nparent(i: number): number {\n    return Math.floor((i - 1) / 2); // Floor division\n}\n
my_heap.dart
/* Get index of left child node */\nint _left(int i) {\n  return 2 * i + 1;\n}\n\n/* Get index of right child node */\nint _right(int i) {\n  return 2 * i + 2;\n}\n\n/* Get index of parent node */\nint _parent(int i) {\n  return (i - 1) ~/ 2; // Floor division\n}\n
my_heap.rs
/* Get index of left child node */\nfn left(i: usize) -> usize {\n    2 * i + 1\n}\n\n/* Get index of right child node */\nfn right(i: usize) -> usize {\n    2 * i + 2\n}\n\n/* Get index of parent node */\nfn parent(i: usize) -> usize {\n    (i - 1) / 2 // Floor division\n}\n
my_heap.c
/* Get index of left child node */\nint left(MaxHeap *maxHeap, int i) {\n    return 2 * i + 1;\n}\n\n/* Get index of right child node */\nint right(MaxHeap *maxHeap, int i) {\n    return 2 * i + 2;\n}\n\n/* Get index of parent node */\nint parent(MaxHeap *maxHeap, int i) {\n    return (i - 1) / 2; // Round down\n}\n
my_heap.kt
/* Get index of left child node */\nfun left(i: Int): Int {\n    return 2 * i + 1\n}\n\n/* Get index of right child node */\nfun right(i: Int): Int {\n    return 2 * i + 2\n}\n\n/* Get index of parent node */\nfun parent(i: Int): Int {\n    return (i - 1) / 2 // Floor division\n}\n
my_heap.rb
### Get left child index ###\ndef left(i)\n  2 * i + 1\nend\n\n### Get right child index ###\ndef right(i)\n  2 * i + 2\nend\n\n### Get parent node index ###\ndef parent(i)\n  (i - 1) / 2     # Floor division\nend\n
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#2-accessing-the-heap-top-element","level":3,"title":"2.   Accessing the Heap Top Element","text":"

The heap top element is the root node of the binary tree, which is also the first element of the list:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
def peek(self) -> int:\n    \"\"\"Access top element\"\"\"\n    return self.max_heap[0]\n
my_heap.cpp
/* Access top element */\nint peek() {\n    return maxHeap[0];\n}\n
my_heap.java
/* Access top element */\nint peek() {\n    return maxHeap.get(0);\n}\n
my_heap.cs
/* Access top element */\nint Peek() {\n    return maxHeap[0];\n}\n
my_heap.go
/* Access top element */\nfunc (h *maxHeap) peek() any {\n    return h.data[0]\n}\n
my_heap.swift
/* Access top element */\nfunc peek() -> Int {\n    maxHeap[0]\n}\n
my_heap.js
/* Access top element */\npeek() {\n    return this.#maxHeap[0];\n}\n
my_heap.ts
/* Access top element */\npeek(): number {\n    return this.maxHeap[0];\n}\n
my_heap.dart
/* Access top element */\nint peek() {\n  return _maxHeap[0];\n}\n
my_heap.rs
/* Access top element */\nfn peek(&self) -> Option<i32> {\n    self.max_heap.first().copied()\n}\n
my_heap.c
/* Access top element */\nint peek(MaxHeap *maxHeap) {\n    return maxHeap->data[0];\n}\n
my_heap.kt
/* Access top element */\nfun peek(): Int {\n    return maxHeap[0]\n}\n
my_heap.rb
### Access heap top element ###\ndef peek\n  @max_heap[0]\nend\n
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#3-inserting-an-element-into-the-heap","level":3,"title":"3.   Inserting an Element Into the Heap","text":"

Given an element val, we first add it to the bottom of the heap. After addition, since val may be larger than other elements in the heap, the heap's property may be violated. Therefore, it's necessary to repair the path from the inserted node to the root node. This operation is called heapify.

Starting from the inserted node, perform heapify from bottom to top. As shown in Figure 8-3, we compare the inserted node with its parent node, and if the inserted node is larger, swap them. Then continue this operation, repairing nodes in the heap from bottom to top until we pass the root node or encounter a node that does not need swapping.

<1><2><3><4><5><6><7><8><9>

Figure 8-3   Steps of inserting an element into the heap

Given a total of \\(n\\) nodes, the tree height is \\(O(\\log n)\\). Thus, the number of loop iterations in the heapify operation is at most \\(O(\\log n)\\), making the time complexity of the element insertion operation \\(O(\\log n)\\). The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
def push(self, val: int):\n    \"\"\"Element enters heap\"\"\"\n    # Add node\n    self.max_heap.append(val)\n    # Heapify from bottom to top\n    self.sift_up(self.size() - 1)\n\ndef sift_up(self, i: int):\n    \"\"\"Starting from node i, heapify from bottom to top\"\"\"\n    while True:\n        # Get parent node of node i\n        p = self.parent(i)\n        # When \"crossing root node\" or \"node needs no repair\", end heapify\n        if p < 0 or self.max_heap[i] <= self.max_heap[p]:\n            break\n        # Swap two nodes\n        self.swap(i, p)\n        # Loop upward heapify\n        i = p\n
my_heap.cpp
/* Element enters heap */\nvoid push(int val) {\n    // Add node\n    maxHeap.push_back(val);\n    // Heapify from bottom to top\n    siftUp(size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nvoid siftUp(int i) {\n    while (true) {\n        // Get parent node of node i\n        int p = parent(i);\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || maxHeap[i] <= maxHeap[p])\n            break;\n        // Swap two nodes\n        swap(maxHeap[i], maxHeap[p]);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.java
/* Element enters heap */\nvoid push(int val) {\n    // Add node\n    maxHeap.add(val);\n    // Heapify from bottom to top\n    siftUp(size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nvoid siftUp(int i) {\n    while (true) {\n        // Get parent node of node i\n        int p = parent(i);\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || maxHeap.get(i) <= maxHeap.get(p))\n            break;\n        // Swap two nodes\n        swap(i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.cs
/* Element enters heap */\nvoid Push(int val) {\n    // Add node\n    maxHeap.Add(val);\n    // Heapify from bottom to top\n    SiftUp(Size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nvoid SiftUp(int i) {\n    while (true) {\n        // Get parent node of node i\n        int p = Parent(i);\n        // If 'past root node' or 'node needs no repair', end heapify\n        if (p < 0 || maxHeap[i] <= maxHeap[p])\n            break;\n        // Swap two nodes\n        Swap(i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.go
/* Element enters heap */\nfunc (h *maxHeap) push(val any) {\n    // Add node\n    h.data = append(h.data, val)\n    // Heapify from bottom to top\n    h.siftUp(len(h.data) - 1)\n}\n\n/* Starting from node i, heapify from bottom to top */\nfunc (h *maxHeap) siftUp(i int) {\n    for true {\n        // Get parent node of node i\n        p := h.parent(i)\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if p < 0 || h.data[i].(int) <= h.data[p].(int) {\n            break\n        }\n        // Swap two nodes\n        h.swap(i, p)\n        // Loop upward heapify\n        i = p\n    }\n}\n
my_heap.swift
/* Element enters heap */\nfunc push(val: Int) {\n    // Add node\n    maxHeap.append(val)\n    // Heapify from bottom to top\n    siftUp(i: size() - 1)\n}\n\n/* Starting from node i, heapify from bottom to top */\nfunc siftUp(i: Int) {\n    var i = i\n    while true {\n        // Get parent node of node i\n        let p = parent(i: i)\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if p < 0 || maxHeap[i] <= maxHeap[p] {\n            break\n        }\n        // Swap two nodes\n        swap(i: i, j: p)\n        // Loop upward heapify\n        i = p\n    }\n}\n
my_heap.js
/* Element enters heap */\npush(val) {\n    // Add node\n    this.#maxHeap.push(val);\n    // Heapify from bottom to top\n    this.#siftUp(this.size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\n#siftUp(i) {\n    while (true) {\n        // Get parent node of node i\n        const p = this.#parent(i);\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break;\n        // Swap two nodes\n        this.#swap(i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.ts
/* Element enters heap */\npush(val: number): void {\n    // Add node\n    this.maxHeap.push(val);\n    // Heapify from bottom to top\n    this.siftUp(this.size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nsiftUp(i: number): void {\n    while (true) {\n        // Get parent node of node i\n        const p = this.parent(i);\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break;\n        // Swap two nodes\n        this.swap(i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.dart
/* Element enters heap */\nvoid push(int val) {\n  // Add node\n  _maxHeap.add(val);\n  // Heapify from bottom to top\n  siftUp(size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nvoid siftUp(int i) {\n  while (true) {\n    // Get parent node of node i\n    int p = _parent(i);\n    // When \"crossing root node\" or \"node needs no repair\", end heapify\n    if (p < 0 || _maxHeap[i] <= _maxHeap[p]) {\n      break;\n    }\n    // Swap two nodes\n    _swap(i, p);\n    // Loop upward heapify\n    i = p;\n  }\n}\n
my_heap.rs
/* Element enters heap */\nfn push(&mut self, val: i32) {\n    // Add node\n    self.max_heap.push(val);\n    // Heapify from bottom to top\n    self.sift_up(self.size() - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nfn sift_up(&mut self, mut i: usize) {\n    loop {\n        // Node i is already the heap root, end heapification\n        if i == 0 {\n            break;\n        }\n        // Get parent node of node i\n        let p = Self::parent(i);\n        // When \"node needs no repair\", end heapification\n        if self.max_heap[i] <= self.max_heap[p] {\n            break;\n        }\n        // Swap two nodes\n        self.swap(i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.c
/* Element enters heap */\nvoid push(MaxHeap *maxHeap, int val) {\n    // By default, should not add this many nodes\n    if (maxHeap->size == MAX_SIZE) {\n        printf(\"heap is full!\");\n        return;\n    }\n    // Add node\n    maxHeap->data[maxHeap->size] = val;\n    maxHeap->size++;\n\n    // Heapify from bottom to top\n    siftUp(maxHeap, maxHeap->size - 1);\n}\n\n/* Starting from node i, heapify from bottom to top */\nvoid siftUp(MaxHeap *maxHeap, int i) {\n    while (true) {\n        // Get parent node of node i\n        int p = parent(maxHeap, i);\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) {\n            break;\n        }\n        // Swap two nodes\n        swap(maxHeap, i, p);\n        // Loop upward heapify\n        i = p;\n    }\n}\n
my_heap.kt
/* Element enters heap */\nfun push(_val: Int) {\n    // Add node\n    maxHeap.add(_val)\n    // Heapify from bottom to top\n    siftUp(size() - 1)\n}\n\n/* Starting from node i, heapify from bottom to top */\nfun siftUp(it: Int) {\n    // Kotlin function parameters are immutable, so create temporary variable\n    var i = it\n    while (true) {\n        // Get parent node of node i\n        val p = parent(i)\n        // When \"crossing root node\" or \"node needs no repair\", end heapify\n        if (p < 0 || maxHeap[i] <= maxHeap[p]) break\n        // Swap two nodes\n        swap(i, p)\n        // Loop upward heapify\n        i = p\n    }\n}\n
my_heap.rb
### Push element to heap ###\ndef push(val)\n  # Add node\n  @max_heap << val\n  # Heapify from bottom to top\n  sift_up(size - 1)\nend\n\n### Heapify from node i, bottom to top ###\ndef sift_up(i)\n  loop do\n    # Get parent node of node i\n    p = parent(i)\n    # When \"crossing root node\" or \"node needs no repair\", end heapify\n    break if p < 0 || @max_heap[i] <= @max_heap[p]\n    # Swap two nodes\n    swap(i, p)\n    # Loop upward heapify\n    i = p\n  end\nend\n
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#4-removing-the-heap-top-element","level":3,"title":"4.   Removing the Heap Top Element","text":"

The heap top element is the root node of the binary tree, which is the first element of the list. If we directly remove the first element from the list, all node indexes in the binary tree would change, making subsequent repair with heapify difficult. To minimize changes in element indexes, we use the following steps.

  1. Swap the heap top element with the heap bottom element (swap the root node with the rightmost leaf node).
  2. After swapping, remove the heap bottom from the list (note that since we've swapped, we're actually removing the original heap top element).
  3. Starting from the root node, perform heapify from top to bottom.

As shown in Figure 8-4, the direction of \"top-to-bottom heapify\" is opposite to \"bottom-to-top heapify\". We compare the root node's value with its two children and swap it with the largest child. Then loop this operation until we pass a leaf node or encounter a node that doesn't need swapping.

<1><2><3><4><5><6><7><8><9><10>

Figure 8-4   Steps of removing the heap top element

Similar to the element insertion operation, the time complexity of the heap top element removal operation is also \\(O(\\log n)\\). The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
def pop(self) -> int:\n    \"\"\"Element exits heap\"\"\"\n    # Handle empty case\n    if self.is_empty():\n        raise IndexError(\"Heap is empty\")\n    # Swap root node with rightmost leaf node (swap first element with last element)\n    self.swap(0, self.size() - 1)\n    # Delete node\n    val = self.max_heap.pop()\n    # Heapify from top to bottom\n    self.sift_down(0)\n    # Return top element\n    return val\n\ndef sift_down(self, i: int):\n    \"\"\"Starting from node i, heapify from top to bottom\"\"\"\n    while True:\n        # Find node with largest value among i, l, r, denoted as ma\n        l, r, ma = self.left(i), self.right(i), i\n        if l < self.size() and self.max_heap[l] > self.max_heap[ma]:\n            ma = l\n        if r < self.size() and self.max_heap[r] > self.max_heap[ma]:\n            ma = r\n        # If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        if ma == i:\n            break\n        # Swap two nodes\n        self.swap(i, ma)\n        # Loop downward heapify\n        i = ma\n
my_heap.cpp
/* Element exits heap */\nvoid pop() {\n    // Handle empty case\n    if (isEmpty()) {\n        throw out_of_range(\"Heap is empty\");\n    }\n    // Delete node\n    swap(maxHeap[0], maxHeap[size() - 1]);\n    // Remove node\n    maxHeap.pop_back();\n    // Return top element\n    siftDown(0);\n}\n\n/* Starting from node i, heapify from top to bottom */\nvoid siftDown(int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = left(i), r = right(i), ma = i;\n        if (l < size() && maxHeap[l] > maxHeap[ma])\n            ma = l;\n        if (r < size() && maxHeap[r] > maxHeap[ma])\n            ma = r;\n        // Swap two nodes\n        if (ma == i)\n            break;\n        swap(maxHeap[i], maxHeap[ma]);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.java
/* Element exits heap */\nint pop() {\n    // Handle empty case\n    if (isEmpty())\n        throw new IndexOutOfBoundsException();\n    // Delete node\n    swap(0, size() - 1);\n    // Remove node\n    int val = maxHeap.remove(size() - 1);\n    // Return top element\n    siftDown(0);\n    // Return heap top element\n    return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\nvoid siftDown(int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = left(i), r = right(i), ma = i;\n        if (l < size() && maxHeap.get(l) > maxHeap.get(ma))\n            ma = l;\n        if (r < size() && maxHeap.get(r) > maxHeap.get(ma))\n            ma = r;\n        // Swap two nodes\n        if (ma == i)\n            break;\n        // Swap two nodes\n        swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.cs
/* Element exits heap */\nint Pop() {\n    // Handle empty case\n    if (IsEmpty())\n        throw new IndexOutOfRangeException();\n    // Delete node\n    Swap(0, Size() - 1);\n    // Remove node\n    int val = maxHeap.Last();\n    maxHeap.RemoveAt(Size() - 1);\n    // Return top element\n    SiftDown(0);\n    // Return heap top element\n    return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\nvoid SiftDown(int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = Left(i), r = Right(i), ma = i;\n        if (l < Size() && maxHeap[l] > maxHeap[ma])\n            ma = l;\n        if (r < Size() && maxHeap[r] > maxHeap[ma])\n            ma = r;\n        // If 'node i is largest' or 'past leaf node', end heapify\n        if (ma == i) break;\n        // Swap two nodes\n        Swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.go
/* Element exits heap */\nfunc (h *maxHeap) pop() any {\n    // Handle empty case\n    if h.isEmpty() {\n        fmt.Println(\"error\")\n        return nil\n    }\n    // Delete node\n    h.swap(0, h.size()-1)\n    // Remove node\n    val := h.data[len(h.data)-1]\n    h.data = h.data[:len(h.data)-1]\n    // Return top element\n    h.siftDown(0)\n\n    // Return heap top element\n    return val\n}\n\n/* Starting from node i, heapify from top to bottom */\nfunc (h *maxHeap) siftDown(i int) {\n    for true {\n        // Find node with maximum value among nodes i, l, r, denoted as max\n        l, r, max := h.left(i), h.right(i), i\n        if l < h.size() && h.data[l].(int) > h.data[max].(int) {\n            max = l\n        }\n        if r < h.size() && h.data[r].(int) > h.data[max].(int) {\n            max = r\n        }\n        // Swap two nodes\n        if max == i {\n            break\n        }\n        // Swap two nodes\n        h.swap(i, max)\n        // Loop downwards heapification\n        i = max\n    }\n}\n
my_heap.swift
/* Element exits heap */\nfunc pop() -> Int {\n    // Handle empty case\n    if isEmpty() {\n        fatalError(\"Heap is empty\")\n    }\n    // Delete node\n    swap(i: 0, j: size() - 1)\n    // Remove node\n    let val = maxHeap.remove(at: size() - 1)\n    // Return top element\n    siftDown(i: 0)\n    // Return heap top element\n    return val\n}\n\n/* Starting from node i, heapify from top to bottom */\nfunc siftDown(i: Int) {\n    var i = i\n    while true {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let l = left(i: i)\n        let r = right(i: i)\n        var ma = i\n        if l < size(), maxHeap[l] > maxHeap[ma] {\n            ma = l\n        }\n        if r < size(), maxHeap[r] > maxHeap[ma] {\n            ma = r\n        }\n        // Swap two nodes\n        if ma == i {\n            break\n        }\n        // Swap two nodes\n        swap(i: i, j: ma)\n        // Loop downwards heapification\n        i = ma\n    }\n}\n
my_heap.js
/* Element exits heap */\npop() {\n    // Handle empty case\n    if (this.isEmpty()) throw new Error('Heap is empty');\n    // Delete node\n    this.#swap(0, this.size() - 1);\n    // Remove node\n    const val = this.#maxHeap.pop();\n    // Return top element\n    this.#siftDown(0);\n    // Return heap top element\n    return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\n#siftDown(i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        const l = this.#left(i),\n            r = this.#right(i);\n        let ma = i;\n        if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l;\n        if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r;\n        // Swap two nodes\n        if (ma === i) break;\n        // Swap two nodes\n        this.#swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.ts
/* Element exits heap */\npop(): number {\n    // Handle empty case\n    if (this.isEmpty()) throw new RangeError('Heap is empty.');\n    // Delete node\n    this.swap(0, this.size() - 1);\n    // Remove node\n    const val = this.maxHeap.pop();\n    // Return top element\n    this.siftDown(0);\n    // Return heap top element\n    return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\nsiftDown(i: number): void {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        const l = this.left(i),\n            r = this.right(i);\n        let ma = i;\n        if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l;\n        if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r;\n        // Swap two nodes\n        if (ma === i) break;\n        // Swap two nodes\n        this.swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.dart
/* Element exits heap */\nint pop() {\n  // Handle empty case\n  if (isEmpty()) throw Exception('Heap is empty');\n  // Delete node\n  _swap(0, size() - 1);\n  // Remove node\n  int val = _maxHeap.removeLast();\n  // Return top element\n  siftDown(0);\n  // Return heap top element\n  return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\nvoid siftDown(int i) {\n  while (true) {\n    // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n    int l = _left(i);\n    int r = _right(i);\n    int ma = i;\n    if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l;\n    if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r;\n    // Swap two nodes\n    if (ma == i) break;\n    // Swap two nodes\n    _swap(i, ma);\n    // Loop downwards heapification\n    i = ma;\n  }\n}\n
my_heap.rs
/* Element exits heap */\nfn pop(&mut self) -> i32 {\n    // Handle empty case\n    if self.is_empty() {\n        panic!(\"index out of bounds\");\n    }\n    // Delete node\n    self.swap(0, self.size() - 1);\n    // Remove node\n    let val = self.max_heap.pop().unwrap();\n    // Return top element\n    self.sift_down(0);\n    // Return heap top element\n    val\n}\n\n/* Starting from node i, heapify from top to bottom */\nfn sift_down(&mut self, mut i: usize) {\n    loop {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let (l, r, mut ma) = (Self::left(i), Self::right(i), i);\n        if l < self.size() && self.max_heap[l] > self.max_heap[ma] {\n            ma = l;\n        }\n        if r < self.size() && self.max_heap[r] > self.max_heap[ma] {\n            ma = r;\n        }\n        // Swap two nodes\n        if ma == i {\n            break;\n        }\n        // Swap two nodes\n        self.swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n
my_heap.c
/* Element exits heap */\nint pop(MaxHeap *maxHeap) {\n    // Handle empty case\n    if (isEmpty(maxHeap)) {\n        printf(\"heap is empty!\");\n        return INT_MAX;\n    }\n    // Delete node\n    swap(maxHeap, 0, size(maxHeap) - 1);\n    // Remove node\n    int val = maxHeap->data[maxHeap->size - 1];\n    maxHeap->size--;\n    // Return top element\n    siftDown(maxHeap, 0);\n\n    // Return heap top element\n    return val;\n}\n\n/* Starting from node i, heapify from top to bottom */\nvoid siftDown(MaxHeap *maxHeap, int i) {\n    while (true) {\n        // Find node with maximum value among nodes i, l, r, denoted as max\n        int l = left(maxHeap, i);\n        int r = right(maxHeap, i);\n        int max = i;\n        if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) {\n            max = l;\n        }\n        if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) {\n            max = r;\n        }\n        // Swap two nodes\n        if (max == i) {\n            break;\n        }\n        // Swap two nodes\n        swap(maxHeap, i, max);\n        // Loop downwards heapification\n        i = max;\n    }\n}\n
my_heap.kt
/* Element exits heap */\nfun pop(): Int {\n    // Handle empty case\n    if (isEmpty()) throw IndexOutOfBoundsException()\n    // Delete node\n    swap(0, size() - 1)\n    // Remove node\n    val _val = maxHeap.removeAt(size() - 1)\n    // Return top element\n    siftDown(0)\n    // Return heap top element\n    return _val\n}\n\n/* Starting from node i, heapify from top to bottom */\nfun siftDown(it: Int) {\n    // Kotlin function parameters are immutable, so create temporary variable\n    var i = it\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        val l = left(i)\n        val r = right(i)\n        var ma = i\n        if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l\n        if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r\n        // Swap two nodes\n        if (ma == i) break\n        // Swap two nodes\n        swap(i, ma)\n        // Loop downwards heapification\n        i = ma\n    }\n}\n
my_heap.rb
### Pop element from heap ###\ndef pop\n  # Handle empty case\n  raise IndexError, \"Heap is empty\" if is_empty?\n  # Delete node\n  swap(0, size - 1)\n  # Remove node\n  val = @max_heap.pop\n  # Return top element\n  sift_down(0)\n  # Return heap top element\n  val\nend\n\n### Heapify from node i, top to bottom ###\ndef sift_down(i)\n  loop do\n    # If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n    l, r, ma = left(i), right(i), i\n    ma = l if l < size && @max_heap[l] > @max_heap[ma]\n    ma = r if r < size && @max_heap[r] > @max_heap[ma]\n\n    # Swap two nodes\n    break if ma == i\n\n    # Swap two nodes\n    swap(i, ma)\n    # Loop downwards heapification\n    i = ma\n  end\nend\n
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/heap/#813-common-applications-of-heaps","level":2,"title":"8.1.3   Common Applications of Heaps","text":"
  • Priority queue: Heaps are typically the preferred data structure for implementing priority queues, with both enqueue and dequeue operations having a time complexity of \\(O(\\log n)\\), and the heap construction operation having \\(O(n)\\), all of which are highly efficient.
  • Heap sort: Given a set of data, we can build a heap with them and then continuously perform element removal operations to obtain sorted data. However, we usually use a more elegant approach to implement heap sort, as detailed in the \"Heap Sort\" chapter.
  • Getting the largest \\(k\\) elements: This is a classic algorithm problem and also a typical application, such as selecting the top 10 trending news for Weibo hot search, selecting the top 10 best-selling products, etc.
","path":["Chapter 8. Heap","8.1   Heap"],"tags":[]},{"location":"chapter_heap/summary/","level":1,"title":"8.4   Summary","text":"","path":["Chapter 8. Heap","8.4   Summary"],"tags":[]},{"location":"chapter_heap/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • A heap is a complete binary tree that can be categorized as a max heap or min heap based on its property. The heap top element of a max heap (min heap) is the largest (smallest).
  • A priority queue is defined as a queue with priority sorting, typically implemented using heaps.
  • Common heap operations and their corresponding time complexities include: element insertion \\(O(\\log n)\\), heap top element removal \\(O(\\log n)\\), and accessing the heap top element \\(O(1)\\).
  • Complete binary trees are well-suited for array representation, so we typically use arrays to store heaps.
  • Heapify operations are used to maintain the heap property and are employed in both element insertion and removal operations.
  • The time complexity of building a heap with \\(n\\) input elements can be optimized to \\(O(n)\\), which is highly efficient.
  • Top-k is a classic algorithm problem that can be efficiently solved using the heap data structure, with a time complexity of \\(O(n \\log k)\\).
","path":["Chapter 8. Heap","8.4   Summary"],"tags":[]},{"location":"chapter_heap/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Are the \"heap\" in data structures and the \"heap\" in memory management the same concept?

The two are not the same concept; they just happen to share the name \"heap.\" The heap in computer system memory is part of dynamic memory allocation, where programs can use it to store data during runtime. Programs can request a certain amount of heap memory to store complex structures such as objects and arrays. When this data is no longer needed, the program needs to release this memory to prevent memory leaks. Compared to stack memory, heap memory management and usage require more caution, as improper use can lead to issues such as memory leaks and dangling pointers.

","path":["Chapter 8. Heap","8.4   Summary"],"tags":[]},{"location":"chapter_heap/top_k/","level":1,"title":"8.3   Top-K Problem","text":"

Question

Given an unordered array nums of length \\(n\\), return the largest \\(k\\) elements in the array.

For this problem, we'll first introduce two solutions with relatively straightforward approaches, then introduce a more efficient heap-based solution.

","path":["Chapter 8. Heap","8.3   Top-K Problem"],"tags":[]},{"location":"chapter_heap/top_k/#831-method-1-iterative-selection","level":2,"title":"8.3.1   Method 1: Iterative Selection","text":"

We can perform \\(k\\) rounds of traversal as shown in Figure 8-6, extracting the \\(1^{st}\\), \\(2^{nd}\\), \\(\\dots\\), \\(k^{th}\\) largest elements in each round, with a time complexity of \\(O(nk)\\).

This method is only suitable when \\(k \\ll n\\), because when \\(k\\) is close to \\(n\\), the time complexity approaches \\(O(n^2)\\), which is very time-consuming.

Figure 8-6   Traversing to find the largest k elements

Tip

When \\(k = n\\), we can obtain a complete sorted sequence, which is equivalent to the \"selection sort\" algorithm.

","path":["Chapter 8. Heap","8.3   Top-K Problem"],"tags":[]},{"location":"chapter_heap/top_k/#832-method-2-sorting","level":2,"title":"8.3.2   Method 2: Sorting","text":"

As shown in Figure 8-7, we can first sort the array nums, then return the rightmost \\(k\\) elements, with a time complexity of \\(O(n \\log n)\\).

Clearly, this method \"overachieves\" the task, as we only need to find the largest \\(k\\) elements, without needing to sort the other elements.

Figure 8-7   Sorting to find the largest k elements

","path":["Chapter 8. Heap","8.3   Top-K Problem"],"tags":[]},{"location":"chapter_heap/top_k/#833-method-3-heap","level":2,"title":"8.3.3   Method 3: Heap","text":"

We can solve the Top-k problem more efficiently using heaps, with the process shown in Figure 8-8.

  1. Initialize a min heap, where the heap top element is the smallest.
  2. First, insert the first \\(k\\) elements of the array into the heap in sequence.
  3. Starting from the \\((k + 1)^{th}\\) element, if the current element is greater than the heap top element, remove the heap top element and insert the current element into the heap.
  4. After traversal is complete, the heap contains the largest \\(k\\) elements.
<1><2><3><4><5><6><7><8><9>

Figure 8-8   Finding the largest k elements using a heap

Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby top_k.py
def top_k_heap(nums: list[int], k: int) -> list[int]:\n    \"\"\"Find the largest k elements in array based on heap\"\"\"\n    # Initialize min heap\n    heap = []\n    # Enter the first k elements of array into heap\n    for i in range(k):\n        heapq.heappush(heap, nums[i])\n    # Starting from the (k+1)th element, maintain heap length as k\n    for i in range(k, len(nums)):\n        # If current element is greater than top element, top element exits heap, current element enters heap\n        if nums[i] > heap[0]:\n            heapq.heappop(heap)\n            heapq.heappush(heap, nums[i])\n    return heap\n
top_k.cpp
/* Find the largest k elements in array based on heap */\npriority_queue<int, vector<int>, greater<int>> topKHeap(vector<int> &nums, int k) {\n    // Python's heapq module implements min heap by default\n    priority_queue<int, vector<int>, greater<int>> heap;\n    // Enter the first k elements of array into heap\n    for (int i = 0; i < k; i++) {\n        heap.push(nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (int i = k; i < nums.size(); i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > heap.top()) {\n            heap.pop();\n            heap.push(nums[i]);\n        }\n    }\n    return heap;\n}\n
top_k.java
/* Find the largest k elements in array based on heap */\nQueue<Integer> topKHeap(int[] nums, int k) {\n    // Python's heapq module implements min heap by default\n    Queue<Integer> heap = new PriorityQueue<Integer>();\n    // Enter the first k elements of array into heap\n    for (int i = 0; i < k; i++) {\n        heap.offer(nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (int i = k; i < nums.length; i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > heap.peek()) {\n            heap.poll();\n            heap.offer(nums[i]);\n        }\n    }\n    return heap;\n}\n
top_k.cs
/* Find the largest k elements in array based on heap */\nPriorityQueue<int, int> TopKHeap(int[] nums, int k) {\n    // Python's heapq module implements min heap by default\n    PriorityQueue<int, int> heap = new();\n    // Enter the first k elements of array into heap\n    for (int i = 0; i < k; i++) {\n        heap.Enqueue(nums[i], nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (int i = k; i < nums.Length; i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > heap.Peek()) {\n            heap.Dequeue();\n            heap.Enqueue(nums[i], nums[i]);\n        }\n    }\n    return heap;\n}\n
top_k.go
/* Find the largest k elements in array based on heap */\nfunc topKHeap(nums []int, k int) *minHeap {\n    // Python's heapq module implements min heap by default\n    h := &minHeap{}\n    heap.Init(h)\n    // Enter the first k elements of array into heap\n    for i := 0; i < k; i++ {\n        heap.Push(h, nums[i])\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for i := k; i < len(nums); i++ {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if nums[i] > h.Top().(int) {\n            heap.Pop(h)\n            heap.Push(h, nums[i])\n        }\n    }\n    return h\n}\n
top_k.swift
/* Find the largest k elements in array based on heap */\nfunc topKHeap(nums: [Int], k: Int) -> [Int] {\n    // Initialize min heap and build heap with first k elements\n    var heap = Heap(nums.prefix(k))\n    // Starting from the (k+1)th element, maintain heap length as k\n    for i in nums.indices.dropFirst(k) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if nums[i] > heap.min()! {\n            _ = heap.removeMin()\n            heap.insert(nums[i])\n        }\n    }\n    return heap.unordered\n}\n
top_k.js
/* Element enters heap */\nfunction pushMinHeap(maxHeap, val) {\n    // Negate element\n    maxHeap.push(-val);\n}\n\n/* Element exits heap */\nfunction popMinHeap(maxHeap) {\n    // Negate element\n    return -maxHeap.pop();\n}\n\n/* Access top element */\nfunction peekMinHeap(maxHeap) {\n    // Negate element\n    return -maxHeap.peek();\n}\n\n/* Extract elements from heap */\nfunction getMinHeap(maxHeap) {\n    // Negate element\n    return maxHeap.getMaxHeap().map((num) => -num);\n}\n\n/* Find the largest k elements in array based on heap */\nfunction topKHeap(nums, k) {\n    // Python's heapq module implements min heap by default\n    // Note: We negate all heap elements to simulate min heap using max heap\n    const maxHeap = new MaxHeap([]);\n    // Enter the first k elements of array into heap\n    for (let i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (let i = k; i < nums.length; i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    // Return elements in heap\n    return getMinHeap(maxHeap);\n}\n
top_k.ts
/* Element enters heap */\nfunction pushMinHeap(maxHeap: MaxHeap, val: number): void {\n    // Negate element\n    maxHeap.push(-val);\n}\n\n/* Element exits heap */\nfunction popMinHeap(maxHeap: MaxHeap): number {\n    // Negate element\n    return -maxHeap.pop();\n}\n\n/* Access top element */\nfunction peekMinHeap(maxHeap: MaxHeap): number {\n    // Negate element\n    return -maxHeap.peek();\n}\n\n/* Extract elements from heap */\nfunction getMinHeap(maxHeap: MaxHeap): number[] {\n    // Negate element\n    return maxHeap.getMaxHeap().map((num: number) => -num);\n}\n\n/* Find the largest k elements in array based on heap */\nfunction topKHeap(nums: number[], k: number): number[] {\n    // Python's heapq module implements min heap by default\n    // Note: We negate all heap elements to simulate min heap using max heap\n    const maxHeap = new MaxHeap([]);\n    // Enter the first k elements of array into heap\n    for (let i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (let i = k; i < nums.length; i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    // Return elements in heap\n    return getMinHeap(maxHeap);\n}\n
top_k.dart
/* Find the largest k elements in array based on heap */\nMinHeap topKHeap(List<int> nums, int k) {\n  // Initialize min heap, push first k elements of array to heap\n  MinHeap heap = MinHeap(nums.sublist(0, k));\n  // Starting from the (k+1)th element, maintain heap length as k\n  for (int i = k; i < nums.length; i++) {\n    // If current element is greater than top element, top element exits heap, current element enters heap\n    if (nums[i] > heap.peek()) {\n      heap.pop();\n      heap.push(nums[i]);\n    }\n  }\n  return heap;\n}\n
top_k.rs
/* Find the largest k elements in array based on heap */\nfn top_k_heap(nums: Vec<i32>, k: usize) -> BinaryHeap<Reverse<i32>> {\n    // BinaryHeap is a max heap, use Reverse to negate elements to implement min heap\n    let mut heap = BinaryHeap::<Reverse<i32>>::new();\n    // Enter the first k elements of array into heap\n    for &num in nums.iter().take(k) {\n        heap.push(Reverse(num));\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for &num in nums.iter().skip(k) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if num > heap.peek().unwrap().0 {\n            heap.pop();\n            heap.push(Reverse(num));\n        }\n    }\n    heap\n}\n
top_k.c
/* Element enters heap */\nvoid pushMinHeap(MaxHeap *maxHeap, int val) {\n    // Negate element\n    push(maxHeap, -val);\n}\n\n/* Element exits heap */\nint popMinHeap(MaxHeap *maxHeap) {\n    // Negate element\n    return -pop(maxHeap);\n}\n\n/* Access top element */\nint peekMinHeap(MaxHeap *maxHeap) {\n    // Negate element\n    return -peek(maxHeap);\n}\n\n/* Extract elements from heap */\nint *getMinHeap(MaxHeap *maxHeap) {\n    // Negate all heap elements and store in res array\n    int *res = (int *)malloc(maxHeap->size * sizeof(int));\n    for (int i = 0; i < maxHeap->size; i++) {\n        res[i] = -maxHeap->data[i];\n    }\n    return res;\n}\n\n/* Extract elements from heap */\nint *getMinHeap(MaxHeap *maxHeap) {\n    // Negate all heap elements and store in res array\n    int *res = (int *)malloc(maxHeap->size * sizeof(int));\n    for (int i = 0; i < maxHeap->size; i++) {\n        res[i] = -maxHeap->data[i];\n    }\n    return res;\n}\n\n// Function to find k largest elements in array using heap\nint *topKHeap(int *nums, int sizeNums, int k) {\n    // Python's heapq module implements min heap by default\n    // Note: We negate all heap elements to simulate min heap using max heap\n    int *empty = (int *)malloc(0);\n    MaxHeap *maxHeap = newMaxHeap(empty, 0);\n    // Enter the first k elements of array into heap\n    for (int i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (int i = k; i < sizeNums; i++) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    int *res = getMinHeap(maxHeap);\n    // Free memory\n    delMaxHeap(maxHeap);\n    return res;\n}\n
top_k.kt
/* Find the largest k elements in array based on heap */\nfun topKHeap(nums: IntArray, k: Int): Queue<Int> {\n    // Python's heapq module implements min heap by default\n    val heap = PriorityQueue<Int>()\n    // Enter the first k elements of array into heap\n    for (i in 0..<k) {\n        heap.offer(nums[i])\n    }\n    // Starting from the (k+1)th element, maintain heap length as k\n    for (i in k..<nums.size) {\n        // If current element is greater than top element, top element exits heap, current element enters heap\n        if (nums[i] > heap.peek()) {\n            heap.poll()\n            heap.offer(nums[i])\n        }\n    }\n    return heap\n}\n
top_k.rb
### Find largest k elements in array using heap ###\ndef top_k_heap(nums, k)\n  # Python's heapq module implements min heap by default\n  # Note: We negate all heap elements to simulate min heap using max heap\n  max_heap = MaxHeap.new([])\n\n  # Enter the first k elements of array into heap\n  for i in 0...k\n    push_min_heap(max_heap, nums[i])\n  end\n\n  # Starting from the (k+1)th element, maintain heap length as k\n  for i in k...nums.length\n    # If current element is greater than top element, top element exits heap, current element enters heap\n    if nums[i] > peek_min_heap(max_heap)\n      pop_min_heap(max_heap)\n      push_min_heap(max_heap, nums[i])\n    end\n  end\n\n  get_min_heap(max_heap)\nend\n

A total of \\(n\\) rounds of heap insertions and removals are performed, with the heap's maximum length being \\(k\\), so the time complexity is \\(O(n \\log k)\\). This method is very efficient; when \\(k\\) is small, the time complexity approaches \\(O(n)\\); when \\(k\\) is large, the time complexity does not exceed \\(O(n \\log n)\\).

Additionally, this method is suitable for dynamic data stream scenarios. By continuously adding data, we can maintain the elements in the heap, thus achieving dynamic updates of the largest \\(k\\) elements.

","path":["Chapter 8. Heap","8.3   Top-K Problem"],"tags":[]},{"location":"chapter_hello_algo/","level":1,"title":"Before Starting","text":"

A few years ago, I shared the \"Sword for Offer\" problem solutions on LeetCode, receiving encouragement and support from many readers. During interactions with readers, the most frequently asked question I encountered was \"how to get started with algorithms.\" Gradually, I developed a keen interest in this question.

Diving straight into problem-solving seems to be the most popular approach—it's simple, direct, and effective. However, problem-solving is like playing Minesweeper: those with strong self-learning abilities can successfully defuse the mines one by one, while those with insufficient foundations may end up bruised and battered, retreating step by step in frustration. Reading through textbooks is also a common practice, but for job seekers, graduation theses, resume submissions, and preparations for written tests and interviews have already consumed most of their energy, making working through thick books an arduous challenge.

If you're facing similar struggles, then it's fortunate that this book has \"found\" you. This book is my answer to this question—even if it may not be the optimal solution, it is at least a positive attempt. While this book alone won't directly land you a job offer, it will guide you to explore the \"knowledge map\" of data structures and algorithms, help you understand the shapes, sizes, and distributions of different \"mines,\" and enable you to master various \"mine-clearing methods.\" With these skills, I believe you can tackle problems and read technical literature more confidently, gradually building a complete knowledge system.

I deeply agree with Professor Feynman's words: \"Knowledge isn't free. You have to pay attention.\" In this sense, this book is not entirely \"free.\" In order to live up to the precious \"attention\" you invest in this book, I will do my utmost and devote my greatest \"attention\" to completing this work.

I'm acutely aware of my limited knowledge and shallow expertise. Although the content of this book has been refined over a period of time, there are certainly still many errors, and I sincerely welcome critiques and corrections from teachers and fellow students.

Hello, Algorithms!

The advent of computers has brought tremendous changes to the world. With their high-speed computing capabilities and excellent programmability, they have become the ideal medium for executing algorithms and processing data. Whether it's the realistic graphics in video games, the intelligent decision-making in autonomous driving, AlphaGo's brilliant Go matches, or ChatGPT's natural interactions, these applications are all exquisite interpretations of algorithms on computers.

In fact, before the advent of computers, algorithms and data structures already existed in every corner of the world. Early algorithms were relatively simple, such as ancient counting methods and tool-making procedures. As civilization progressed, algorithms gradually became more refined and complex. From the ingenious craftsmanship of master artisans, to industrial products that liberate productive forces, to the scientific laws governing the operation of the universe, behind almost every ordinary or astonishing thing lies ingenious algorithmic thinking.

Similarly, data structures are everywhere: from large-scale social networks to small subway systems, many systems can be modeled as \"graphs\"; from a nation to a family, the primary organizational forms of society exhibit characteristics of \"trees\"; winter clothing is like a \"stack,\" where the first item put on is the last to be taken off; a badminton tube is like a \"queue,\" with items inserted at one end and retrieved from the other; a dictionary is like a \"hash table,\" enabling quick lookup of target entries.

This book aims to help readers understand the core concepts of algorithms and data structures through clear and accessible animated illustrations and runnable code examples, and to implement them through programming. Building on this foundation, the book endeavors to reveal the vivid manifestations of algorithms in the complex world and showcase the beauty of algorithms. I hope this book can be of help to you!

","path":["Before Starting"],"tags":[]},{"location":"chapter_introduction/","level":1,"title":"Chapter 1.   Encounter with Algorithms","text":"

Abstract

A young girl dances gracefully, intertwined with data, her skirt flowing with the melody of algorithms.

She invites you to dance with her. Follow her steps closely and enter the world of algorithms, full of logic and beauty.

","path":["Chapter 1. Encounter With Algorithms","Chapter 1.   Encounter with Algorithms"],"tags":[]},{"location":"chapter_introduction/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 1.1   Algorithms Are Everywhere
  • 1.2   What Is an Algorithm
  • 1.3   Summary
","path":["Chapter 1. Encounter With Algorithms","Chapter 1.   Encounter with Algorithms"],"tags":[]},{"location":"chapter_introduction/algorithms_are_everywhere/","level":1,"title":"1.1   Algorithms Are Everywhere","text":"

When we hear the term \"algorithm,\" we naturally think of mathematics. However, many algorithms do not involve complex mathematics but rely more on basic logic, which can be seen everywhere in our daily lives.

Before we start discussing about algorithms officially, there's an interesting fact worth sharing: you've learned many algorithms unconsciously and are used to applying them in your daily life. Here, I will give a few specific examples to prove this point.

Example 1: Looking Up a Dictionary. In an English dictionary, words are listed alphabetically. Assuming we're searching for a word that starts with the letter \\(r\\), this is typically done in the following way:

  1. Open the dictionary to about halfway and check the first vocabulary of the page, let's say the letter starts with \\(m\\).
  2. Since \\(r\\) comes after \\(m\\) in the alphabet, the first half can be ignored and the search space is narrowed down to the second half.
  3. Repeat steps 1. and 2. until you find the page where the word starts with \\(r\\).
<1><2><3><4><5>

Figure 1-1   Process of looking up a dictionary

Looking up a dictionary, an essential skill for elementary school students is actually the famous \"Binary Search\" algorithm. From a data structure perspective, we can consider the dictionary as a sorted \"array\"; from an algorithmic perspective, the series of actions taken to look up a word in the dictionary can be viewed as the algorithm \"Binary Search.\"

Example 2: Organizing Card Deck. When playing cards, we need to arrange the cards in our hands in ascending order, as shown in the following process.

  1. Divide the playing cards into \"ordered\" and \"unordered\" sections, assuming initially the leftmost card is already in order.
  2. Take out a card from the unordered section and insert it into the correct position in the ordered section; after this, the leftmost two cards are in order.
  3. Repeat step 2 until all cards are in order.

Figure 1-2   Process of sorting a deck of cards

The above method of organizing playing cards is practically the \"Insertion Sort\" algorithm, which is very efficient for small datasets. Many programming languages' sorting functions include the insertion sort.

Example 3: Making Change. Assume making a purchase of \\(69\\) at a supermarket. If you give the cashier \\(100\\), they will need to provide you with \\(31\\) in change. This process can be clearly understood as illustrated in Figure 1-3.

  1. The options are currencies valued below \\(31\\), including \\(1\\), \\(5\\), \\(10\\), and \\(20\\).
  2. Take out the largest \\(20\\) from the options, leaving \\(31 - 20 = 11\\).
  3. Take out the largest \\(10\\) from the remaining options, leaving \\(11 - 10 = 1\\).
  4. Take out the largest \\(1\\) from the remaining options, leaving \\(1 - 1 = 0\\).
  5. Complete change-making, the solution is \\(20 + 10 + 1 = 31\\).

Figure 1-3   Process of making change

In the steps described, we choose the best option at each stage by utilizing the largest denomination available, which leads to an effective change-making strategy. From a data structures and algorithms perspective, this approach is known as a \"Greedy\" algorithm.

From cooking a meal to interstellar travel, almost all problem-solving involves algorithms. The advent of computers allows us to store data structures in memory and write code to call the CPU and GPU to execute algorithms. In this way, we can transfer real-life problems to computers and solve various complex issues in a more efficient way.

Tip

If you are still confused about concepts like data structures, algorithms, arrays, and binary searches, I encourage you to keep reading. This book will gently guide you into the realm of understanding data structures and algorithms.

","path":["Chapter 1. Encounter With Algorithms","1.1   Algorithms Are Everywhere"],"tags":[]},{"location":"chapter_introduction/summary/","level":1,"title":"1.3   Summary","text":"","path":["Chapter 1. Encounter With Algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Algorithms are ubiquitous in daily life and are not distant, esoteric knowledge. In fact, we have already learned many algorithms unconsciously and use them to solve problems big and small in life.
  • The principle of looking up a dictionary is consistent with the binary search algorithm. Binary search embodies the important algorithmic idea of divide and conquer.
  • The process of organizing playing cards is very similar to the insertion sort algorithm. Insertion sort is suitable for sorting small datasets.
  • The steps of making change are essentially a greedy algorithm, where the best choice is made at each step based on the current situation.
  • An algorithm is a set of instructions or operational steps that solves a specific problem within a finite amount of time, while a data structure is the way computers organize and store data.
  • Data structures and algorithms are closely connected. Data structures are the foundation of algorithms, and algorithms breathe life into data structures.
  • We can compare data structures and algorithms to assembling building blocks. The blocks represent data, the shape and connection method of the blocks represent the data structure, and the steps to assemble the blocks correspond to the algorithm.
","path":["Chapter 1. Encounter With Algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: As a programmer, I have never used algorithms to solve problems in my daily work. Common algorithms are already encapsulated by programming languages and can be used directly. Does this mean that the problems in our work have not yet reached the level where algorithms are needed?

If we compare specific work skills to \"techniques\" in martial arts, then fundamental subjects should be more like \"internal skills\".

I believe the significance of learning algorithms (and other fundamental subjects) is not to implement them from scratch at work, but rather to be able to make professional reactions and judgments when solving problems based on the knowledge learned, thereby improving the overall quality of work. Here is a simple example. Every programming language has a built-in sorting function:

  • If we have not studied data structures and algorithms, we might simply feed any given data to this sorting function. It runs smoothly with good performance, and there doesn't seem to be any problem.
  • But if we have studied algorithms, we would know that the time complexity of the built-in sorting function is \\(O(n \\log n)\\). However, if the given data consists of integers with a fixed number of digits (such as student IDs), we can use the more efficient \"radix sort\", reducing the time complexity to \\(O(nk)\\), where \\(k\\) is the number of digits. When the data volume is very large, the saved running time can create significant value (reduced costs, improved experience, etc.).

In the field of engineering, a large number of problems are difficult to reach optimal solutions, and many problems are only solved \"approximately\". The difficulty of a problem depends on one hand on the nature of the problem itself, and on the other hand on the knowledge reserve of the person observing the problem. The more complete a person's knowledge and the more experience they have, the deeper their analysis of the problem will be, and the more elegantly the problem can be solved.

","path":["Chapter 1. Encounter With Algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/","level":1,"title":"1.2   What Is an Algorithm","text":"","path":["Chapter 1. Encounter With Algorithms","1.2   What Is an Algorithm"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#121-algorithm-definition","level":2,"title":"1.2.1   Algorithm Definition","text":"

An algorithm is a set of instructions or operational steps that solves a specific problem within a finite amount of time. It has the following characteristics.

  • The problem is well-defined, with clear input and output definitions.
  • It is feasible and can be completed within a finite number of steps, time, and memory space.
  • Each step has a definite meaning, and under the same input and operating conditions, the output is always the same.
","path":["Chapter 1. Encounter With Algorithms","1.2   What Is an Algorithm"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#122-data-structure-definition","level":2,"title":"1.2.2   Data Structure Definition","text":"

A data structure is a way of organizing and storing data, covering the data content, relationships between data, and methods for data operations. It has the following design objectives.

  • Occupy as little space as possible to save computer memory.
  • Data operations should be as fast as possible, covering data access, addition, deletion, update, etc.
  • Provide a concise data representation and logical information so that algorithms can run efficiently.

Data structure design is a process full of trade-offs. If we want to achieve improvements in one aspect, we often need to make compromises in another aspect. Here are two examples.

  • Compared to arrays, linked lists are more convenient for data addition and deletion operations but sacrifice data access speed.
  • Compared to linked lists, graphs provide richer logical information but require larger memory space.
","path":["Chapter 1. Encounter With Algorithms","1.2   What Is an Algorithm"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#123-the-relationship-between-data-structures-and-algorithms","level":2,"title":"1.2.3   The Relationship Between Data Structures and Algorithms","text":"

As shown in Figure 1-4, data structures and algorithms are highly related and tightly coupled, specifically manifested in the following three aspects.

  • Data structures are the foundation of algorithms. Data structures provide algorithms with structured storage of data and methods for operating on data.
  • Algorithms breathe life into data structures. Data structures themselves only store data information; combined with algorithms, they can solve specific problems.
  • Algorithms can usually be implemented based on different data structures, but execution efficiency may vary greatly. Choosing the appropriate data structure is key.

Figure 1-4   The relationship between data structures and algorithms

Data structures and algorithms are like assembling building blocks as shown in Figure 1-5. A set of building blocks, in addition to containing many parts, also comes with detailed assembly instructions. By following the instructions step by step, we can assemble an exquisite building block model.

Figure 1-5   Assembling blocks

The detailed correspondence between the two is shown in Table 1-1.

Table 1-1   Comparing data structures and algorithms to assembling building blocks

Data structures and algorithms Assembling building blocks Input data Unassembled building blocks Data structure Organization form of building blocks, including shape, size, connection method, etc. Algorithm A series of operational steps to assemble the blocks into the target form Output data Building block model

It is worth noting that data structures and algorithms are independent of programming languages. For this reason, this book is able to provide implementations based on multiple programming languages.

Conventional abbreviation

In actual discussions, we usually abbreviate \"data structures and algorithms\" as \"algorithms\". For example, the well-known LeetCode algorithm problems actually examine knowledge of both data structures and algorithms.

","path":["Chapter 1. Encounter With Algorithms","1.2   What Is an Algorithm"],"tags":[]},{"location":"chapter_preface/","level":1,"title":"Chapter 0.   Preface","text":"

Abstract

Algorithms are like a beautiful symphony, each line of code flows like a melody.

May this book gently resonate in your mind, leaving a unique and profound melody.

","path":["Chapter 0. Preface","Chapter 0.   Preface"],"tags":[]},{"location":"chapter_preface/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 0.1   About This Book
  • 0.2   How to Use This Book
  • 0.3   Summary
","path":["Chapter 0. Preface","Chapter 0.   Preface"],"tags":[]},{"location":"chapter_preface/about_the_book/","level":1,"title":"0.1   About This Book","text":"

This project aims to create an open-source, free, beginner-friendly introductory tutorial on data structures and algorithms.

  • The entire book uses animated illustrations, with clear and easy-to-understand content and a smooth learning curve, guiding beginners to explore the knowledge map of data structures and algorithms.
  • The source code can be run with one click, helping readers improve their programming skills through practice and understand how algorithms work and the underlying implementation of data structures.
  • We encourage readers to learn from each other, and everyone is welcome to ask questions and share insights in the comments section, making progress together through discussion and exchange.
","path":["Chapter 0. Preface","0.1   About This Book"],"tags":[]},{"location":"chapter_preface/about_the_book/#011-target-audience","level":2,"title":"0.1.1   Target Audience","text":"

If you are an algorithm beginner who has never been exposed to algorithms, or if you already have some problem-solving experience and have a vague understanding of data structures and algorithms, oscillating between knowing and not knowing, then this book is tailor-made for you!

If you have already accumulated a certain amount of problem-solving experience and are familiar with most question types, this book can help you review and organize your algorithm knowledge system, and the repository's source code can be used as a \"problem-solving toolkit\" or \"algorithm dictionary.\"

If you are an algorithm \"expert,\" we look forward to receiving your valuable suggestions, or participating in creation together.

Prerequisites

You need to have at least a programming foundation in any language, and be able to read and write simple code.

","path":["Chapter 0. Preface","0.1   About This Book"],"tags":[]},{"location":"chapter_preface/about_the_book/#012-content-structure","level":2,"title":"0.1.2   Content Structure","text":"

The main content of this book is shown in Figure 0-1.

  • Complexity analysis: Evaluation dimensions and methods for data structures and algorithms. Methods for calculating time complexity and space complexity, common types, examples, etc.
  • Data structures: Classification methods for basic data types and data structures. The definition, advantages and disadvantages, common operations, common types, typical applications, implementation methods, etc. of data structures such as arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs.
  • Algorithms: The definition, advantages and disadvantages, efficiency, application scenarios, problem-solving steps, and example problems of algorithms such as searching, sorting, divide and conquer, backtracking, dynamic programming, and greedy algorithms.

Figure 0-1   Main content of this book

","path":["Chapter 0. Preface","0.1   About This Book"],"tags":[]},{"location":"chapter_preface/about_the_book/#013-acknowledgements","level":2,"title":"0.1.3   Acknowledgements","text":"

This book has been continuously improved through the joint efforts of many contributors in the open-source community. Thanks to every contributor who invested time and effort, they are (in the order automatically generated by GitHub): krahets, coderonion, Gonglja, nuomi1, Reanon, justin-tse, hpstory, danielsss, curtishd, night-cruise, S-N-O-R-L-A-X, rongyi, msk397, gvenusleo, khoaxuantu, rivertwilight, K3v123, gyt95, zhuoqinyue, yuelinxin, Zuoxun, mingXta, Phoenix0415, FangYuan33, GN-Yu, longsizhuo, IsChristina, xBLACKICEx, guowei-gong, Cathay-Chen, pengchzn, QiLOL, magentaqin, hello-ikun, JoseHung, qualifier1024, thomasq0, sunshinesDL, L-Super, Guanngxu, Transmigration-zhou, WSL0809, Slone123c, lhxsm, yuan0221, what-is-me, Shyam-Chen, theNefelibatas, longranger2, codeberg-user, xiongsp, JeffersonHuang, prinpal, seven1240, Wonderdch, malone6, xiaomiusa87, gaofer, bluebean-cloud, a16su, SamJin98, hongyun-robot, nanlei, XiaChuerwu, yd-j, iron-irax, mgisr, steventimes, junminhong, heshuyue, danny900714, MolDuM, Nigh, Dr-XYZ, XC-Zero, reeswell, PXG-XPG, NI-SW, Horbin-Magician, Enlightenus, YangXuanyi, beatrix-chan, DullSword, xjr7670, jiaxianhua, qq909244296, iStig, boloboloda, hts0000, gledfish, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, linyejoe2, liuxjerry, llql1211, fbigm, echo1937, szu17dmy, dshlstarr, Yucao-cy, coderlef, czruby, bongbongbakudan, beintentional, ZongYangL, ZhongYuuu, ZhongGuanbin, hezhizhen, linzeyan, ZJKung, luluxia, xb534, ztkuaikuai, yw-1021, ElaBosak233, baagod, zhouLion, yishangzhang, yi427, yanedie, yabo083, weibk, wangwang105, th1nk3r-ing, tao363, 4yDX3906, syd168, sslmj2020, smilelsb, siqyka, selear, sdshaoda, Xi-Row, popozhu, nuquist19, noobcodemaker, XiaoK29, chadyi, lyl625760, lucaswangdev, 0130w, shanghai-Jerry, EJackYang, Javesun99, eltociear, lipusheng, KNChiu, BlindTerran, ShiMaRing, lovelock, FreddieLi, FloranceYeh, fanchenggang, gltianwen, goerll, nedchu, curly210102, CuB3y0nd, KraHsu, CarrotDLaw, youshaoXG, bubble9um, Asashishi, Asa0oo0o0o, fanenr, eagleanurag, akshiterate, 52coder, foursevenlove, KorsChen, GaochaoZhu, hopkings2008, yang-le, realwujing, Evilrabbit520, Umer-Jahangir, Turing-1024-Lee, Suremotoo, paoxiaomooo, Chieko-Seren, Allen-Scai, ymmmas, Risuntsy, Richard-Zhang1019, RafaelCaso, qingpeng9802, primexiao, Urbaner3, zhongfq, nidhoggfgg, MwumLi, CreatorMetaSky, martinx, ZnYang2018, hugtyftg, logan-qiu, psychelzh, Keynman, KeiichiKasai, and KawaiiAsh.

The code review work for this book was completed by coderonion, curtishd, Gonglja, gvenusleo, hpstory, justin-tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon and rongyi (in alphabetical order). Thanks to them for the time and effort they put in, it is they who ensure the standardization and unity of code in various languages.

The Traditional Chinese version of this book was reviewed by Shyam-Chen and Dr-XYZ, the English version was reviewed by yuelinxin, K3v123, QiLOL, Phoenix0415, SamJin98, yanedie, RafaelCaso, pengchzn, thomasq0 and magentaqin, and the Japanese edition was reviewed by eltociear. It is because of their continuous contributions that this book can serve a wider readership, and we thank them.

The ePub ebook generation tool for this book was developed by zhongfq. We thank him for his contribution, which provides readers with a more flexible way to read.

During the creation of this book, I received help from many people.

  • Thanks to my mentor at the company, Dr. Li Xi, who encouraged me to \"take action quickly\" during a conversation, strengthening my determination to write this book;
  • Thanks to my girlfriend Bubble as the first reader of this book, who provided many valuable suggestions from the perspective of an algorithm beginner, making this book more suitable for novices to read;
  • Thanks to Tengbao, Qibao, and Feibao for coming up with a creative name for this book, evoking everyone's fond memories of writing their first line of code \"Hello World!\";
  • Thanks to Xiaoquan for providing professional help in intellectual property rights, which played an important role in the improvement of this open-source book;
  • Thanks to Sutong for designing the beautiful cover and logo for this book, and for patiently making revisions multiple times driven by my obsessive-compulsive disorder;
  • Thanks to @squidfunk for the typesetting suggestions, as well as for developing the open-source documentation theme Material-for-MkDocs.

During the writing process, I read many textbooks and articles on data structures and algorithms. These works provided excellent examples for this book and ensured the accuracy and quality of the book's content. I would like to thank all the teachers and predecessors for their outstanding contributions!

This book advocates a learning method that combines hands and brain, and in this regard I was deeply inspired by Dive into Deep Learning. I highly recommend this excellent work to all readers.

Heartfelt thanks to my parents, it is your support and encouragement that has given me the opportunity to do this interesting thing.

","path":["Chapter 0. Preface","0.1   About This Book"],"tags":[]},{"location":"chapter_preface/suggestions/","level":1,"title":"0.2   How to Use This Book","text":"

Tip

For the best reading experience, it is recommended that you read through this section.

","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/suggestions/#021-writing-style-conventions","level":2,"title":"0.2.1   Writing Style Conventions","text":"
  • Titles marked with * are optional sections with relatively difficult content. If you have limited time, you can skip them first.
  • Technical terms will be in bold (in paper and PDF versions) or underlined (in web versions), such as array. It is recommended to memorize them for reading literature.
  • Key content and summary statements will be bolded, and such text deserves special attention.
  • Words and phrases with specific meanings will be marked with \"quotation marks\" to avoid ambiguity.
  • When it comes to nouns that are inconsistent between programming languages, this book uses Python as the standard, for example, using None to represent \"null\".
  • This book partially abandons the comment conventions of programming languages in favor of more compact content layout. Comments are mainly divided into three types: title comments, content comments, and multi-line comments.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
\"\"\"Title comment, used to label functions, classes, test cases, etc.\"\"\"\n\n# Content comment, used to explain code in detail\n\n\"\"\"\nMulti-line\ncomment\n\"\"\"\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n// Multi-line\n// comment\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
/* Title comment, used to label functions, classes, test cases, etc. */\n\n// Content comment, used to explain code in detail\n\n/**\n * Multi-line\n * comment\n */\n
### Title comment, used to label functions, classes, test cases, etc. ###\n\n# Content comment, used to explain code in detail\n\n# Multi-line\n# comment\n
","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/suggestions/#022-learning-efficiently-with-animated-illustrations","level":2,"title":"0.2.2   Learning Efficiently with Animated Illustrations","text":"

Compared to text, videos and images have higher information density and structural organization, making them easier to understand. In this book, key and difficult knowledge will mainly be presented in the form of animated illustrations, with text serving as explanation and supplement.

If you find that a section of content provides animated illustrations as shown in Figure 0-2 while reading this book, please focus on the illustrations first, with text as a supplement, and combine the two to understand the content.

Figure 0-2   Example of animated illustrations

","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/suggestions/#023-deepening-understanding-through-code-practice","level":2,"title":"0.2.3   Deepening Understanding Through Code Practice","text":"

The accompanying code for this book is hosted in the GitHub repository. As shown in Figure 0-3, the source code comes with test cases and can be run with one click.

If time permits, it is recommended that you type out the code yourself. If you have limited study time, please at least read through and run all the code.

Compared to reading code, the process of writing code often brings more rewards. Learning by doing is the real learning.

Figure 0-3   Example of running code

The preliminary work for running code is mainly divided into three steps.

Step 1: Install the local programming environment. Please follow the tutorial shown in the appendix for installation. If already installed, you can skip this step.

Step 2: Clone or download the code repository. Visit the GitHub repository. If you have already installed Git, you can clone this repository with the following command:

git clone https://github.com/krahets/hello-algo.git\n

Of course, you can also click the \"Download ZIP\" button at the location shown in Figure 0-4 to directly download the code compressed package, and then extract it locally.

Figure 0-4   Clone repository and download code

Step 3: Run the source code. As shown in Figure 0-5, for code blocks with file names at the top, we can find the corresponding source code files in the codes folder of the repository. The source code files can be run with one click, which will help you save unnecessary debugging time and allow you to focus on learning content.

Figure 0-5   Code blocks and corresponding source code files

In addition to running code locally, the web version also supports visual running of Python code (implemented based on pythontutor). As shown in Figure 0-6, you can click \"Visual Run\" below the code block to expand the view and observe the execution process of the algorithm code; you can also click \"Full Screen View\" for a better viewing experience.

Figure 0-6   Visual running of Python code

","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/suggestions/#024-growing-together-through-questions-and-discussions","level":2,"title":"0.2.4   Growing Together Through Questions and Discussions","text":"

When reading this book, please do not easily skip knowledge points that you have not learned well. Feel free to ask your questions in the comments section, and my friends and I will do our best to answer you, and generally reply within two days.

As shown in Figure 0-7, the web version has a comments section at the bottom of each chapter. I hope you will pay more attention to the content of the comments section. On the one hand, you can learn about the problems that everyone encounters, thus checking for omissions and stimulating deeper thinking. On the other hand, I hope you can generously answer other friends' questions, share your insights, and help others progress.

Figure 0-7   Example of comments section

","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/suggestions/#025-algorithm-learning-roadmap","level":2,"title":"0.2.5   Algorithm Learning Roadmap","text":"

From an overall perspective, we can divide the process of learning data structures and algorithms into three stages.

  1. Stage 1: Algorithm introduction. We need to familiarize ourselves with the characteristics and usage of various data structures, and learn the principles, processes, uses, and efficiency of different algorithms.
  2. Stage 2: Practice algorithm problems. It is recommended to start with popular problems, and accumulate at least 100 problems first, to familiarize yourself with mainstream algorithm problems. When first practicing problems, \"knowledge forgetting\" may be a challenge, but rest assured, this is very normal. We can review problems according to the \"Ebbinghaus forgetting curve\", and usually after 3-5 rounds of repetition, we can firmly remember them. For recommended problem lists and practice plans, please see this GitHub repository.
  3. Stage 3: Building a knowledge system. In terms of learning, we can read algorithm column articles, problem-solving frameworks, and algorithm textbooks to continuously enrich our knowledge system. In terms of practicing problems, we can try advanced problem-solving strategies, such as categorization by topic, one problem multiple solutions, one solution multiple problems, etc. Related problem-solving insights can be found in various communities.

As shown in Figure 0-8, the content of this book mainly covers \"Stage 1\", aiming to help you more efficiently carry out Stage 2 and Stage 3 learning.

Figure 0-8   Algorithm learning roadmap

","path":["Chapter 0. Preface","0.2   How to Use This Book"],"tags":[]},{"location":"chapter_preface/summary/","level":1,"title":"0.3   Summary","text":"","path":["Chapter 0. Preface","0.3   Summary"],"tags":[]},{"location":"chapter_preface/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • The main audience of this book is algorithm beginners. If you already have a certain foundation, this book can help you systematically review algorithm knowledge, and the source code in the book can also be used as a \"problem-solving toolkit.\"
  • The content of the book mainly includes three parts: complexity analysis, data structures, and algorithms, covering most topics in this field.
  • For algorithm novices, reading an introductory book during the initial learning stage is crucial, as it can help you avoid many detours.
  • The animated illustrations in the book are usually used to introduce key and difficult knowledge. When reading this book, you should pay more attention to these contents.
  • Practice is the best way to learn programming. It is strongly recommended to run the source code and type the code yourself.
  • The web version of this book has a comments section for each chapter, where you are welcome to share your questions and insights at any time.
","path":["Chapter 0. Preface","0.3   Summary"],"tags":[]},{"location":"chapter_reference/","level":1,"title":"References","text":"

[1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition).

[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition).

[3] Robert Sedgewick, et al. Algorithms (4th Edition).

[4] Yan Weimin. Data Structures (C Language Version).

[5] Deng Junhui. Data Structures (C++ Language Version, Third Edition).

[6] Mark Allen Weiss, translated by Chen Yue. Data Structures and Algorithm Analysis in Java (Third Edition).

[7] Cheng Jie. Conversational Data Structures.

[8] Wang Zheng. The Beauty of Data Structures and Algorithms.

[9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition).

[10] Aston Zhang, et al. Dive into Deep Learning.

","path":["References"],"tags":[]},{"location":"chapter_searching/","level":1,"title":"Chapter 10.   Searching","text":"

Abstract

Searching is an adventure into the unknown, where we may need to traverse every corner of the mysterious space, or we may be able to quickly lock onto the target.

In this journey of discovery, each exploration may yield an unexpected answer.

","path":["Chapter 10. Searching","Chapter 10.   Searching"],"tags":[]},{"location":"chapter_searching/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 10.1   Binary Search
  • 10.2   Binary Search Insertion
  • 10.3   Binary Search Edge Cases
  • 10.4   Hash Optimization Strategy
  • 10.5   Search Algorithms Revisited
  • 10.6   Summary
","path":["Chapter 10. Searching","Chapter 10.   Searching"],"tags":[]},{"location":"chapter_searching/binary_search/","level":1,"title":"10.1   Binary Search","text":"

Binary search is an efficient searching algorithm based on the divide-and-conquer strategy. It leverages the orderliness of data to reduce the search range by half in each round until the target element is found or the search interval becomes empty.

Question

Given an array nums of length \\(n\\) with elements arranged in ascending order and no duplicates, search for and return the index of element target in the array. If the array does not contain the element, return \\(-1\\). An example is shown in Figure 10-1.

Figure 10-1   Binary search example data

As shown in Figure 10-2, we first initialize pointers \\(i = 0\\) and \\(j = n - 1\\), pointing to the first and last elements of the array respectively, representing the search interval \\([0, n - 1]\\). Note that square brackets denote a closed interval, which includes the boundary values themselves.

Next, perform the following two steps in a loop:

  1. Calculate the midpoint index \\(m = \\lfloor {(i + j) / 2} \\rfloor\\), where \\(\\lfloor \\: \\rfloor\\) denotes the floor operation.
  2. Compare nums[m] and target, which results in three cases:
    1. When nums[m] < target, it indicates that target is in the interval \\([m + 1, j]\\), so execute \\(i = m + 1\\).
    2. When nums[m] > target, it indicates that target is in the interval \\([i, m - 1]\\), so execute \\(j = m - 1\\).
    3. When nums[m] = target, it indicates that target has been found, so return index \\(m\\).

If the array does not contain the target element, the search interval will eventually shrink to empty. In this case, return \\(-1\\).

<1><2><3><4><5><6><7>

Figure 10-2   Binary search process

It's worth noting that since both \\(i\\) and \\(j\\) are of int type, \\(i + j\\) may exceed the range of the int type. To avoid large number overflow, we typically use the formula \\(m = \\lfloor {i + (j - i) / 2} \\rfloor\\) to calculate the midpoint.

The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search.py
def binary_search(nums: list[int], target: int) -> int:\n    \"\"\"Binary search (closed interval)\"\"\"\n    # Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    i, j = 0, len(nums) - 1\n    # Loop, exit when the search interval is empty (empty when i > j)\n    while i <= j:\n        # In theory, Python numbers can be infinitely large (depending on memory size), no need to consider large number overflow\n        m = (i + j) // 2  # Calculate midpoint index m\n        if nums[m] < target:\n            i = m + 1  # This means target is in the interval [m+1, j]\n        elif nums[m] > target:\n            j = m - 1  # This means target is in the interval [i, m-1]\n        else:\n            return m  # Found the target element, return its index\n    return -1  # Target element not found, return -1\n
binary_search.cpp
/* Binary search (closed interval on both sides) */\nint binarySearch(vector<int> &nums, int target) {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    int i = 0, j = nums.size() - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // This means target is in the interval [m+1, j]\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m-1]\n            j = m - 1;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.java
/* Binary search (closed interval on both sides) */\nint binarySearch(int[] nums, int target) {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    int i = 0, j = nums.length - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) // This means target is in the interval [m+1, j]\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m-1]\n            j = m - 1;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.cs
/* Binary search (closed interval on both sides) */\nint BinarySearch(int[] nums, int target) {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    int i = 0, j = nums.Length - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2;   // Calculate the midpoint index m\n        if (nums[m] < target)      // This means target is in the interval [m+1, j]\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m-1]\n            j = m - 1;\n        else                       // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.go
/* Binary search (closed interval on both sides) */\nfunc binarySearch(nums []int, target int) int {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    i, j := 0, len(nums)-1\n    // Loop, exit when the search interval is empty (empty when i > j)\n    for i <= j {\n        m := i + (j-i)/2      // Calculate the midpoint index m\n        if nums[m] < target { // This means target is in the interval [m+1, j]\n            i = m + 1\n        } else if nums[m] > target { // This means target is in the interval [i, m-1]\n            j = m - 1\n        } else { // Found the target element, return its index\n            return m\n        }\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.swift
/* Binary search (closed interval on both sides) */\nfunc binarySearch(nums: [Int], target: Int) -> Int {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target { // This means target is in the interval [m+1, j]\n            i = m + 1\n        } else if nums[m] > target { // This means target is in the interval [i, m-1]\n            j = m - 1\n        } else { // Found the target element, return its index\n            return m\n        }\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.js
/* Binary search (closed interval on both sides) */\nfunction binarySearch(nums, target) {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    let i = 0,\n        j = nums.length - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        // Calculate midpoint index m, use parseInt() to round down\n        const m = parseInt(i + (j - i) / 2);\n        if (nums[m] < target)\n            // This means target is in the interval [m+1, j]\n            i = m + 1;\n        else if (nums[m] > target)\n            // This means target is in the interval [i, m-1]\n            j = m - 1;\n        else return m; // Found the target element, return its index\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.ts
/* Binary search (closed interval on both sides) */\nfunction binarySearch(nums: number[], target: number): number {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    let i = 0,\n        j = nums.length - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        // Calculate the midpoint index m\n        const m = Math.floor(i + (j - i) / 2);\n        if (nums[m] < target) {\n            // This means target is in the interval [m+1, j]\n            i = m + 1;\n        } else if (nums[m] > target) {\n            // This means target is in the interval [i, m-1]\n            j = m - 1;\n        } else {\n            // Found the target element, return its index\n            return m;\n        }\n    }\n    return -1; // Target element not found, return -1\n}\n
binary_search.dart
/* Binary search (closed interval on both sides) */\nint binarySearch(List<int> nums, int target) {\n  // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n  int i = 0, j = nums.length - 1;\n  // Loop, exit when the search interval is empty (empty when i > j)\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      // This means target is in the interval [m+1, j]\n      i = m + 1;\n    } else if (nums[m] > target) {\n      // This means target is in the interval [i, m-1]\n      j = m - 1;\n    } else {\n      // Found the target element, return its index\n      return m;\n    }\n  }\n  // Target element not found, return -1\n  return -1;\n}\n
binary_search.rs
/* Binary search (closed interval on both sides) */\nfn binary_search(nums: &[i32], target: i32) -> i32 {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    let mut i = 0;\n    let mut j = nums.len() as i32 - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            // This means target is in the interval [m+1, j]\n            i = m + 1;\n        } else if nums[m as usize] > target {\n            // This means target is in the interval [i, m-1]\n            j = m - 1;\n        } else {\n            // Found the target element, return its index\n            return m;\n        }\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.c
/* Binary search (closed interval on both sides) */\nint binarySearch(int *nums, int len, int target) {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    int i = 0, j = len - 1;\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // This means target is in the interval [m+1, j]\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m-1]\n            j = m - 1;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.kt
/* Binary search (closed interval on both sides) */\nfun binarySearch(nums: IntArray, target: Int): Int {\n    // Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n    var i = 0\n    var j = nums.size - 1\n    // Loop, exit when the search interval is empty (empty when i > j)\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) // This means target is in the interval [m+1, j]\n            i = m + 1\n        else if (nums[m] > target) // This means target is in the interval [i, m-1]\n            j = m - 1\n        else  // Found the target element, return its index\n            return m\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.rb
### Binary search (closed interval) ###\ndef binary_search(nums, target)\n  # Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array\n  i, j = 0, nums.length - 1\n\n  # Loop, exit when the search interval is empty (empty when i > j)\n  while i <= j\n    # In theory, Ruby numbers can be infinitely large (limited by memory), no need to consider overflow\n    m = (i + j) / 2   # Calculate the midpoint index m\n\n    if nums[m] < target\n      i = m + 1 # This means target is in the interval [m+1, j]\n    elsif nums[m] > target\n      j = m - 1 # This means target is in the interval [i, m-1]\n    else\n      return m  # Found the target element, return its index\n    end\n  end\n\n  -1  # Target element not found, return -1\nend\n

Time complexity is \\(O(\\log n)\\): In the binary loop, the interval is reduced by half each round, so the number of loops is \\(\\log_2 n\\).

Space complexity is \\(O(1)\\): Pointers \\(i\\) and \\(j\\) use constant-size space.

","path":["Chapter 10. Searching","10.1   Binary Search"],"tags":[]},{"location":"chapter_searching/binary_search/#1011-interval-representation-methods","level":2,"title":"10.1.1   Interval Representation Methods","text":"

In addition to the closed interval mentioned above, another common interval representation is the \"left-closed right-open\" interval, defined as \\([0, n)\\), meaning the left boundary includes itself while the right boundary does not. Under this representation, the interval \\([i, j)\\) is empty when \\(i = j\\).

We can implement a binary search algorithm with the same functionality based on this representation:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search.py
def binary_search_lcro(nums: list[int], target: int) -> int:\n    \"\"\"Binary search (left-closed right-open interval)\"\"\"\n    # Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    i, j = 0, len(nums)\n    # Loop, exit when the search interval is empty (empty when i = j)\n    while i < j:\n        m = (i + j) // 2  # Calculate midpoint index m\n        if nums[m] < target:\n            i = m + 1  # This means target is in the interval [m+1, j)\n        elif nums[m] > target:\n            j = m  # This means target is in the interval [i, m)\n        else:\n            return m  # Found the target element, return its index\n    return -1  # Target element not found, return -1\n
binary_search.cpp
/* Binary search (left-closed right-open interval) */\nint binarySearchLCRO(vector<int> &nums, int target) {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    int i = 0, j = nums.size();\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // This means target is in the interval [m+1, j)\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m)\n            j = m;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.java
/* Binary search (left-closed right-open interval) */\nint binarySearchLCRO(int[] nums, int target) {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    int i = 0, j = nums.length;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) // This means target is in the interval [m+1, j)\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m)\n            j = m;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.cs
/* Binary search (left-closed right-open interval) */\nint BinarySearchLCRO(int[] nums, int target) {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    int i = 0, j = nums.Length;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2;   // Calculate the midpoint index m\n        if (nums[m] < target)      // This means target is in the interval [m+1, j)\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m)\n            j = m;\n        else                       // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.go
/* Binary search (left-closed right-open interval) */\nfunc binarySearchLCRO(nums []int, target int) int {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    i, j := 0, len(nums)\n    // Loop, exit when the search interval is empty (empty when i = j)\n    for i < j {\n        m := i + (j-i)/2      // Calculate the midpoint index m\n        if nums[m] < target { // This means target is in the interval [m+1, j)\n            i = m + 1\n        } else if nums[m] > target { // This means target is in the interval [i, m)\n            j = m\n        } else { // Found the target element, return its index\n            return m\n        }\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.swift
/* Binary search (left-closed right-open interval) */\nfunc binarySearchLCRO(nums: [Int], target: Int) -> Int {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    var i = nums.startIndex\n    var j = nums.endIndex\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while i < j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target { // This means target is in the interval [m+1, j)\n            i = m + 1\n        } else if nums[m] > target { // This means target is in the interval [i, m)\n            j = m\n        } else { // Found the target element, return its index\n            return m\n        }\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.js
/* Binary search (left-closed right-open interval) */\nfunction binarySearchLCRO(nums, target) {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    let i = 0,\n        j = nums.length;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        // Calculate midpoint index m, use parseInt() to round down\n        const m = parseInt(i + (j - i) / 2);\n        if (nums[m] < target)\n            // This means target is in the interval [m+1, j)\n            i = m + 1;\n        else if (nums[m] > target)\n            // This means target is in the interval [i, m)\n            j = m;\n        // Found the target element, return its index\n        else return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.ts
/* Binary search (left-closed right-open interval) */\nfunction binarySearchLCRO(nums: number[], target: number): number {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    let i = 0,\n        j = nums.length;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        // Calculate the midpoint index m\n        const m = Math.floor(i + (j - i) / 2);\n        if (nums[m] < target) {\n            // This means target is in the interval [m+1, j)\n            i = m + 1;\n        } else if (nums[m] > target) {\n            // This means target is in the interval [i, m)\n            j = m;\n        } else {\n            // Found the target element, return its index\n            return m;\n        }\n    }\n    return -1; // Target element not found, return -1\n}\n
binary_search.dart
/* Binary search (left-closed right-open interval) */\nint binarySearchLCRO(List<int> nums, int target) {\n  // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n  int i = 0, j = nums.length;\n  // Loop, exit when the search interval is empty (empty when i = j)\n  while (i < j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      // This means target is in the interval [m+1, j)\n      i = m + 1;\n    } else if (nums[m] > target) {\n      // This means target is in the interval [i, m)\n      j = m;\n    } else {\n      // Found the target element, return its index\n      return m;\n    }\n  }\n  // Target element not found, return -1\n  return -1;\n}\n
binary_search.rs
/* Binary search (left-closed right-open interval) */\nfn binary_search_lcro(nums: &[i32], target: i32) -> i32 {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    let mut i = 0;\n    let mut j = nums.len() as i32;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while i < j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            // This means target is in the interval [m+1, j)\n            i = m + 1;\n        } else if nums[m as usize] > target {\n            // This means target is in the interval [i, m)\n            j = m;\n        } else {\n            // Found the target element, return its index\n            return m;\n        }\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.c
/* Binary search (left-closed right-open interval) */\nint binarySearchLCRO(int *nums, int len, int target) {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    int i = 0, j = len;\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // This means target is in the interval [m+1, j)\n            i = m + 1;\n        else if (nums[m] > target) // This means target is in the interval [i, m)\n            j = m;\n        else // Found the target element, return its index\n            return m;\n    }\n    // Target element not found, return -1\n    return -1;\n}\n
binary_search.kt
/* Binary search (left-closed right-open interval) */\nfun binarySearchLCRO(nums: IntArray, target: Int): Int {\n    // Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n    var i = 0\n    var j = nums.size\n    // Loop, exit when the search interval is empty (empty when i = j)\n    while (i < j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) // This means target is in the interval [m+1, j)\n            i = m + 1\n        else if (nums[m] > target) // This means target is in the interval [i, m)\n            j = m\n        else  // Found the target element, return its index\n            return m\n    }\n    // Target element not found, return -1\n    return -1\n}\n
binary_search.rb
### Binary search (left-closed right-open interval) ###\ndef binary_search_lcro(nums, target)\n  # Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1\n  i, j = 0, nums.length\n\n  # Loop, exit when the search interval is empty (empty when i = j)\n  while i < j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # This means target is in the interval [m+1, j)\n    elsif nums[m] > target\n      j = m - 1 # This means target is in the interval [i, m)\n    else\n      return m  # Found the target element, return its index\n    end\n  end\n\n  -1  # Target element not found, return -1\nend\n

As shown in Figure 10-3, under the two interval representations, the initialization, loop condition, and interval narrowing operations of the binary search algorithm are all different.

Since both the left and right boundaries in the \"closed interval\" representation are defined as closed, the operations to narrow the interval through pointers \\(i\\) and \\(j\\) are also symmetric. This makes it less error-prone, so the \"closed interval\" approach is generally recommended.

Figure 10-3   Two interval definitions

","path":["Chapter 10. Searching","10.1   Binary Search"],"tags":[]},{"location":"chapter_searching/binary_search/#1012-advantages-and-limitations","level":2,"title":"10.1.2   Advantages and Limitations","text":"

Binary search performs well in both time and space aspects.

  • Binary search has high time efficiency. With large data volumes, the logarithmic time complexity has significant advantages. For example, when the data size \\(n = 2^{20}\\), linear search requires \\(2^{20} = 1048576\\) loop rounds, while binary search only needs \\(\\log_2 2^{20} = 20\\) rounds.
  • Binary search requires no extra space. Compared to searching algorithms that require additional space (such as hash-based search), binary search is more space-efficient.

However, binary search is not suitable for all situations, mainly for the following reasons:

  • Binary search is only applicable to sorted data. If the input data is unsorted, sorting specifically to use binary search would be counterproductive, as sorting algorithms typically have a time complexity of \\(O(n \\log n)\\), which is higher than both linear search and binary search. For scenarios with frequent element insertions, maintaining array orderliness requires inserting elements at specific positions with a time complexity of \\(O(n)\\), which is also very expensive.
  • Binary search is only applicable to arrays. Binary search requires jump-style (non-contiguous) element access, and jump-style access has low efficiency in linked lists, making it unsuitable for linked lists or data structures based on linked list implementations.
  • For small data volumes, linear search performs better. In linear search, each round requires only 1 comparison operation; while in binary search, it requires 1 addition, 1 division, 1-3 comparison operations, and 1 addition (subtraction), totaling 4-6 unit operations. Therefore, when the data volume \\(n\\) is small, linear search is actually faster than binary search.
","path":["Chapter 10. Searching","10.1   Binary Search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/","level":1,"title":"10.3   Binary Search Edge Cases","text":"","path":["Chapter 10. Searching","10.3   Binary Search Edge Cases"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1031-finding-the-left-boundary","level":2,"title":"10.3.1   Finding the Left Boundary","text":"

Question

Given a sorted array nums of length \\(n\\) that may contain duplicate elements, return the index of the leftmost element target in the array. If the array does not contain the element, return \\(-1\\).

Recall the method for finding the insertion point with binary search. After the search completes, \\(i\\) points to the leftmost target, so finding the insertion point is essentially finding the index of the leftmost target.

Consider implementing the left boundary search using the insertion point finding function. Note that the array may not contain target, which could result in the following two cases:

  • The insertion point index \\(i\\) is out of bounds.
  • The element nums[i] is not equal to target.

When either of these situations occurs, simply return \\(-1\\). The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_edge.py
def binary_search_left_edge(nums: list[int], target: int) -> int:\n    \"\"\"Binary search for the leftmost target\"\"\"\n    # Equivalent to finding the insertion point of target\n    i = binary_search_insertion(nums, target)\n    # Target not found, return -1\n    if i == len(nums) or nums[i] != target:\n        return -1\n    # Found target, return index i\n    return i\n
binary_search_edge.cpp
/* Binary search for the leftmost target */\nint binarySearchLeftEdge(vector<int> &nums, int target) {\n    // Equivalent to finding the insertion point of target\n    int i = binarySearchInsertion(nums, target);\n    // Target not found, return -1\n    if (i == nums.size() || nums[i] != target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.java
/* Binary search for the leftmost target */\nint binarySearchLeftEdge(int[] nums, int target) {\n    // Equivalent to finding the insertion point of target\n    int i = binary_search_insertion.binarySearchInsertion(nums, target);\n    // Target not found, return -1\n    if (i == nums.length || nums[i] != target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.cs
/* Binary search for the leftmost target */\nint BinarySearchLeftEdge(int[] nums, int target) {\n    // Equivalent to finding the insertion point of target\n    int i = binary_search_insertion.BinarySearchInsertion(nums, target);\n    // Target not found, return -1\n    if (i == nums.Length || nums[i] != target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.go
/* Binary search for the leftmost target */\nfunc binarySearchLeftEdge(nums []int, target int) int {\n    // Equivalent to finding the insertion point of target\n    i := binarySearchInsertion(nums, target)\n    // Target not found, return -1\n    if i == len(nums) || nums[i] != target {\n        return -1\n    }\n    // Found target, return index i\n    return i\n}\n
binary_search_edge.swift
/* Binary search for the leftmost target */\nfunc binarySearchLeftEdge(nums: [Int], target: Int) -> Int {\n    // Equivalent to finding the insertion point of target\n    let i = binarySearchInsertion(nums: nums, target: target)\n    // Target not found, return -1\n    if i == nums.endIndex || nums[i] != target {\n        return -1\n    }\n    // Found target, return index i\n    return i\n}\n
binary_search_edge.js
/* Binary search for the leftmost target */\nfunction binarySearchLeftEdge(nums, target) {\n    // Equivalent to finding the insertion point of target\n    const i = binarySearchInsertion(nums, target);\n    // Target not found, return -1\n    if (i === nums.length || nums[i] !== target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.ts
/* Binary search for the leftmost target */\nfunction binarySearchLeftEdge(nums: Array<number>, target: number): number {\n    // Equivalent to finding the insertion point of target\n    const i = binarySearchInsertion(nums, target);\n    // Target not found, return -1\n    if (i === nums.length || nums[i] !== target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.dart
/* Binary search for the leftmost target */\nint binarySearchLeftEdge(List<int> nums, int target) {\n  // Equivalent to finding the insertion point of target\n  int i = binarySearchInsertion(nums, target);\n  // Target not found, return -1\n  if (i == nums.length || nums[i] != target) {\n    return -1;\n  }\n  // Found target, return index i\n  return i;\n}\n
binary_search_edge.rs
/* Binary search for the leftmost target */\nfn binary_search_left_edge(nums: &[i32], target: i32) -> i32 {\n    // Equivalent to finding the insertion point of target\n    let i = binary_search_insertion(nums, target);\n    // Target not found, return -1\n    if i == nums.len() as i32 || nums[i as usize] != target {\n        return -1;\n    }\n    // Found target, return index i\n    i\n}\n
binary_search_edge.c
/* Binary search for the leftmost target */\nint binarySearchLeftEdge(int *nums, int numSize, int target) {\n    // Equivalent to finding the insertion point of target\n    int i = binarySearchInsertion(nums, numSize, target);\n    // Target not found, return -1\n    if (i == numSize || nums[i] != target) {\n        return -1;\n    }\n    // Found target, return index i\n    return i;\n}\n
binary_search_edge.kt
/* Binary search for the leftmost target */\nfun binarySearchLeftEdge(nums: IntArray, target: Int): Int {\n    // Equivalent to finding the insertion point of target\n    val i = binarySearchInsertion(nums, target)\n    // Target not found, return -1\n    if (i == nums.size || nums[i] != target) {\n        return -1\n    }\n    // Found target, return index i\n    return i\n}\n
binary_search_edge.rb
### Binary search leftmost target ###\ndef binary_search_left_edge(nums, target)\n  # Equivalent to finding the insertion point of target\n  i = binary_search_insertion(nums, target)\n\n  # Target not found, return -1\n  return -1 if i == nums.length || nums[i] != target\n\n  i # Found target, return index i\nend\n
","path":["Chapter 10. Searching","10.3   Binary Search Edge Cases"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1032-finding-the-right-boundary","level":2,"title":"10.3.2   Finding the Right Boundary","text":"

So how do we find the rightmost target? The most direct approach is to modify the code and replace the pointer shrinking operation in the nums[m] == target case. The code is omitted here; interested readers can implement it themselves.

Below we introduce two more clever methods.

","path":["Chapter 10. Searching","10.3   Binary Search Edge Cases"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1-reusing-left-boundary-search","level":3,"title":"1.   Reusing Left Boundary Search","text":"

In fact, we can use the function for finding the leftmost element to find the rightmost element. The specific method is: Convert finding the rightmost target into finding the leftmost target + 1.

As shown in Figure 10-7, after the search completes, pointer \\(i\\) points to the leftmost target + 1 (if it exists), while \\(j\\) points to the rightmost target, so we can simply return \\(j\\).

Figure 10-7   Converting right boundary search to left boundary search

Note that the returned insertion point is \\(i\\), so we need to subtract \\(1\\) from it to obtain \\(j\\):

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_edge.py
def binary_search_right_edge(nums: list[int], target: int) -> int:\n    \"\"\"Binary search for the rightmost target\"\"\"\n    # Convert to finding the leftmost target + 1\n    i = binary_search_insertion(nums, target + 1)\n    # j points to the rightmost target, i points to the first element greater than target\n    j = i - 1\n    # Target not found, return -1\n    if j == -1 or nums[j] != target:\n        return -1\n    # Found target, return index j\n    return j\n
binary_search_edge.cpp
/* Binary search for the rightmost target */\nint binarySearchRightEdge(vector<int> &nums, int target) {\n    // Convert to finding the leftmost target + 1\n    int i = binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    int j = i - 1;\n    // Target not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.java
/* Binary search for the rightmost target */\nint binarySearchRightEdge(int[] nums, int target) {\n    // Convert to finding the leftmost target + 1\n    int i = binary_search_insertion.binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    int j = i - 1;\n    // Target not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.cs
/* Binary search for the rightmost target */\nint BinarySearchRightEdge(int[] nums, int target) {\n    // Convert to finding the leftmost target + 1\n    int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    int j = i - 1;\n    // Target not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.go
/* Binary search for the rightmost target */\nfunc binarySearchRightEdge(nums []int, target int) int {\n    // Convert to finding the leftmost target + 1\n    i := binarySearchInsertion(nums, target+1)\n    // j points to the rightmost target, i points to the first element greater than target\n    j := i - 1\n    // Target not found, return -1\n    if j == -1 || nums[j] != target {\n        return -1\n    }\n    // Found target, return index j\n    return j\n}\n
binary_search_edge.swift
/* Binary search for the rightmost target */\nfunc binarySearchRightEdge(nums: [Int], target: Int) -> Int {\n    // Convert to finding the leftmost target + 1\n    let i = binarySearchInsertion(nums: nums, target: target + 1)\n    // j points to the rightmost target, i points to the first element greater than target\n    let j = i - 1\n    // Target not found, return -1\n    if j == -1 || nums[j] != target {\n        return -1\n    }\n    // Found target, return index j\n    return j\n}\n
binary_search_edge.js
/* Binary search for the rightmost target */\nfunction binarySearchRightEdge(nums, target) {\n    // Convert to finding the leftmost target + 1\n    const i = binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    const j = i - 1;\n    // Target not found, return -1\n    if (j === -1 || nums[j] !== target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.ts
/* Binary search for the rightmost target */\nfunction binarySearchRightEdge(nums: Array<number>, target: number): number {\n    // Convert to finding the leftmost target + 1\n    const i = binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    const j = i - 1;\n    // Target not found, return -1\n    if (j === -1 || nums[j] !== target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.dart
/* Binary search for the rightmost target */\nint binarySearchRightEdge(List<int> nums, int target) {\n  // Convert to finding the leftmost target + 1\n  int i = binarySearchInsertion(nums, target + 1);\n  // j points to the rightmost target, i points to the first element greater than target\n  int j = i - 1;\n  // Target not found, return -1\n  if (j == -1 || nums[j] != target) {\n    return -1;\n  }\n  // Found target, return index j\n  return j;\n}\n
binary_search_edge.rs
/* Binary search for the rightmost target */\nfn binary_search_right_edge(nums: &[i32], target: i32) -> i32 {\n    // Convert to finding the leftmost target + 1\n    let i = binary_search_insertion(nums, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    let j = i - 1;\n    // Target not found, return -1\n    if j == -1 || nums[j as usize] != target {\n        return -1;\n    }\n    // Found target, return index j\n    j\n}\n
binary_search_edge.c
/* Binary search for the rightmost target */\nint binarySearchRightEdge(int *nums, int numSize, int target) {\n    // Convert to finding the leftmost target + 1\n    int i = binarySearchInsertion(nums, numSize, target + 1);\n    // j points to the rightmost target, i points to the first element greater than target\n    int j = i - 1;\n    // Target not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Found target, return index j\n    return j;\n}\n
binary_search_edge.kt
/* Binary search for the rightmost target */\nfun binarySearchRightEdge(nums: IntArray, target: Int): Int {\n    // Convert to finding the leftmost target + 1\n    val i = binarySearchInsertion(nums, target + 1)\n    // j points to the rightmost target, i points to the first element greater than target\n    val j = i - 1\n    // Target not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1\n    }\n    // Found target, return index j\n    return j\n}\n
binary_search_edge.rb
### Binary search rightmost target ###\ndef binary_search_right_edge(nums, target)\n  # Convert to finding the leftmost target + 1\n  i = binary_search_insertion(nums, target + 1)\n\n  # j points to the rightmost target, i points to the first element greater than target\n  j = i - 1\n\n  # Target not found, return -1\n  return -1 if j == -1 || nums[j] != target\n\n  j # Found target, return index j\nend\n
","path":["Chapter 10. Searching","10.3   Binary Search Edge Cases"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#2-converting-to-element-search","level":3,"title":"2.   Converting to Element Search","text":"

We know that when the array does not contain target, \\(i\\) and \\(j\\) will eventually point to the first elements greater than and less than target, respectively.

Therefore, as shown in Figure 10-8, we can construct an element that does not exist in the array to find the left and right boundaries.

  • Finding the leftmost target: Can be converted to finding target - 0.5 and returning pointer \\(i\\).
  • Finding the rightmost target: Can be converted to finding target + 0.5 and returning pointer \\(j\\).

Figure 10-8   Converting boundary search to element search

The code is omitted here, but the following two points are worth noting:

  • Since the given array does not contain decimals, we don't need to worry about how to handle equal cases.
  • Because this method introduces decimals, the variable target in the function needs to be changed to a floating-point type (Python does not require this change).
","path":["Chapter 10. Searching","10.3   Binary Search Edge Cases"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/","level":1,"title":"10.2   Binary Search Insertion Point","text":"

Binary search can not only be used to search for target elements but also to solve many variant problems, such as searching for the insertion position of a target element.

","path":["Chapter 10. Searching","10.2   Binary Search Insertion Point"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/#1021-case-without-duplicate-elements","level":2,"title":"10.2.1   Case Without Duplicate Elements","text":"

Question

Given a sorted array nums of length \\(n\\) and an element target, where the array contains no duplicate elements. Insert target into the array nums while maintaining its sorted order. If the array already contains the element target, insert it to its left. Return the index of target in the array after insertion. An example is shown in Figure 10-4.

Figure 10-4   Binary search insertion point example data

If we want to reuse the binary search code from the previous section, we need to answer the following two questions.

Question 1: When the array contains target, is the insertion point index the same as that element's index?

The problem requires inserting target to the left of equal elements, which means the newly inserted target replaces the position of the original target. In other words, when the array contains target, the insertion point index is the index of that target.

Question 2: When the array does not contain target, what is the insertion point index?

Further consider the binary search process: When nums[m] < target, \\(i\\) moves, which means pointer \\(i\\) is approaching elements greater than or equal to target. Similarly, pointer \\(j\\) is always approaching elements less than or equal to target.

Therefore, when the binary search ends, we must have: \\(i\\) points to the first element greater than target, and \\(j\\) points to the first element less than target. It's easy to see that when the array does not contain target, the insertion index is \\(i\\). The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_insertion.py
def binary_search_insertion_simple(nums: list[int], target: int) -> int:\n    \"\"\"Binary search for insertion point (no duplicate elements)\"\"\"\n    i, j = 0, len(nums) - 1  # Initialize closed interval [0, n-1]\n    while i <= j:\n        m = (i + j) // 2  # Calculate midpoint index m\n        if nums[m] < target:\n            i = m + 1  # target is in the interval [m+1, j]\n        elif nums[m] > target:\n            j = m - 1  # target is in the interval [i, m-1]\n        else:\n            return m  # Found target, return insertion point m\n    # Target not found, return insertion point i\n    return i\n
binary_search_insertion.cpp
/* Binary search for insertion point (no duplicate elements) */\nint binarySearchInsertionSimple(vector<int> &nums, int target) {\n    int i = 0, j = nums.size() - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.java
/* Binary search for insertion point (no duplicate elements) */\nint binarySearchInsertionSimple(int[] nums, int target) {\n    int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.cs
/* Binary search for insertion point (no duplicate elements) */\nint BinarySearchInsertionSimple(int[] nums, int target) {\n    int i = 0, j = nums.Length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.go
/* Binary search for insertion point (no duplicate elements) */\nfunc binarySearchInsertionSimple(nums []int, target int) int {\n    // Initialize closed interval [0, n-1]\n    i, j := 0, len(nums)-1\n    for i <= j {\n        // Calculate the midpoint index m\n        m := i + (j-i)/2\n        if nums[m] < target {\n            // target is in the interval [m+1, j]\n            i = m + 1\n        } else if nums[m] > target {\n            // target is in the interval [i, m-1]\n            j = m - 1\n        } else {\n            // Found target, return insertion point m\n            return m\n        }\n    }\n    // Target not found, return insertion point i\n    return i\n}\n
binary_search_insertion.swift
/* Binary search for insertion point (no duplicate elements) */\nfunc binarySearchInsertionSimple(nums: [Int], target: Int) -> Int {\n    // Initialize closed interval [0, n-1]\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target {\n            i = m + 1 // target is in the interval [m+1, j]\n        } else if nums[m] > target {\n            j = m - 1 // target is in the interval [i, m-1]\n        } else {\n            return m // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i\n}\n
binary_search_insertion.js
/* Binary search for insertion point (no duplicate elements) */\nfunction binarySearchInsertionSimple(nums, target) {\n    let i = 0,\n        j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.ts
/* Binary search for insertion point (no duplicate elements) */\nfunction binarySearchInsertionSimple(\n    nums: Array<number>,\n    target: number\n): number {\n    let i = 0,\n        j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.dart
/* Binary search for insertion point (no duplicate elements) */\nint binarySearchInsertionSimple(List<int> nums, int target) {\n  int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1]\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      i = m + 1; // target is in the interval [m+1, j]\n    } else if (nums[m] > target) {\n      j = m - 1; // target is in the interval [i, m-1]\n    } else {\n      return m; // Found target, return insertion point m\n    }\n  }\n  // Target not found, return insertion point i\n  return i;\n}\n
binary_search_insertion.rs
/* Binary search for insertion point (no duplicate elements) */\nfn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 {\n    let (mut i, mut j) = (0, nums.len() as i32 - 1); // Initialize closed interval [0, n-1]\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if nums[m as usize] > target {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m;\n        }\n    }\n    // Target not found, return insertion point i\n    i\n}\n
binary_search_insertion.c
/* Binary search for insertion point (no duplicate elements) */\nint binarySearchInsertionSimple(int *nums, int numSize, int target) {\n    int i = 0, j = numSize - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            return m; // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i;\n}\n
binary_search_insertion.kt
/* Binary search for insertion point (no duplicate elements) */\nfun binarySearchInsertionSimple(nums: IntArray, target: Int): Int {\n    var i = 0\n    var j = nums.size - 1 // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1 // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1 // target is in the interval [i, m-1]\n        } else {\n            return m // Found target, return insertion point m\n        }\n    }\n    // Target not found, return insertion point i\n    return i\n}\n
binary_search_insertion.rb
### Binary search insertion point (no duplicates) ###\ndef binary_search_insertion_simple(nums, target)\n  # Initialize closed interval [0, n-1]\n  i, j = 0, nums.length - 1\n\n  while i <= j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # target is in the interval [m+1, j]\n    elsif nums[m] > target\n      j = m - 1 # target is in the interval [i, m-1]\n    else\n      return m  # Found target, return insertion point m\n    end\n  end\n\n  i # Target not found, return insertion point i\nend\n
","path":["Chapter 10. Searching","10.2   Binary Search Insertion Point"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/#1022-case-with-duplicate-elements","level":2,"title":"10.2.2   Case with Duplicate Elements","text":"

Question

Based on the previous problem, assume the array may contain duplicate elements, with everything else remaining the same.

Suppose there are multiple target elements in the array. Ordinary binary search can only return the index of one target, and cannot determine how many target elements are to the left and right of that element.

The problem requires inserting the target element at the leftmost position, so we need to find the index of the leftmost target in the array. Initially, consider implementing this through the steps shown in Figure 10-5:

  1. Perform binary search to obtain the index of any target, denoted as \\(k\\).
  2. Starting from index \\(k\\), perform linear traversal to the left, and return when the leftmost target is found.

Figure 10-5   Linear search for insertion point of duplicate elements

Although this method works, it includes linear search, resulting in a time complexity of \\(O(n)\\). When the array contains many duplicate target elements, this method is very inefficient.

Now consider extending the binary search code. As shown in Figure 10-6, the overall process remains unchanged: calculate the midpoint index \\(m\\) in each round, then compare target with nums[m], divided into the following cases:

  • When nums[m] < target or nums[m] > target, it means target has not been found yet, so use the ordinary binary search interval narrowing operation to make pointers \\(i\\) and \\(j\\) approach target.
  • When nums[m] == target, it means elements less than target are in the interval \\([i, m - 1]\\), so use \\(j = m - 1\\) to narrow the interval, thereby making pointer \\(j\\) approach elements less than target.

After the loop completes, \\(i\\) points to the leftmost target, and \\(j\\) points to the first element less than target, so index \\(i\\) is the insertion point.

<1><2><3><4><5><6><7><8>

Figure 10-6   Steps for binary search insertion point of duplicate elements

Observe the following code: the operations for branches nums[m] > target and nums[m] == target are the same, so the two can be merged.

Even so, we can still keep the conditional branches expanded, as the logic is clearer and more readable.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_insertion.py
def binary_search_insertion(nums: list[int], target: int) -> int:\n    \"\"\"Binary search for insertion point (with duplicate elements)\"\"\"\n    i, j = 0, len(nums) - 1  # Initialize closed interval [0, n-1]\n    while i <= j:\n        m = (i + j) // 2  # Calculate midpoint index m\n        if nums[m] < target:\n            i = m + 1  # target is in the interval [m+1, j]\n        elif nums[m] > target:\n            j = m - 1  # target is in the interval [i, m-1]\n        else:\n            j = m - 1  # The first element less than target is in the interval [i, m-1]\n    # Return insertion point i\n    return i\n
binary_search_insertion.cpp
/* Binary search for insertion point (with duplicate elements) */\nint binarySearchInsertion(vector<int> &nums, int target) {\n    int i = 0, j = nums.size() - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.java
/* Binary search for insertion point (with duplicate elements) */\nint binarySearchInsertion(int[] nums, int target) {\n    int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.cs
/* Binary search for insertion point (with duplicate elements) */\nint BinarySearchInsertion(int[] nums, int target) {\n    int i = 0, j = nums.Length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.go
/* Binary search for insertion point (with duplicate elements) */\nfunc binarySearchInsertion(nums []int, target int) int {\n    // Initialize closed interval [0, n-1]\n    i, j := 0, len(nums)-1\n    for i <= j {\n        // Calculate the midpoint index m\n        m := i + (j-i)/2\n        if nums[m] < target {\n            // target is in the interval [m+1, j]\n            i = m + 1\n        } else if nums[m] > target {\n            // target is in the interval [i, m-1]\n            j = m - 1\n        } else {\n            // The first element less than target is in the interval [i, m-1]\n            j = m - 1\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
binary_search_insertion.swift
/* Binary search for insertion point (with duplicate elements) */\nfunc binarySearchInsertion(nums: [Int], target: Int) -> Int {\n    // Initialize closed interval [0, n-1]\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target {\n            i = m + 1 // target is in the interval [m+1, j]\n        } else if nums[m] > target {\n            j = m - 1 // target is in the interval [i, m-1]\n        } else {\n            j = m - 1 // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
binary_search_insertion.js
/* Binary search for insertion point (with duplicate elements) */\nfunction binarySearchInsertion(nums, target) {\n    let i = 0,\n        j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.ts
/* Binary search for insertion point (with duplicate elements) */\nfunction binarySearchInsertion(nums: Array<number>, target: number): number {\n    let i = 0,\n        j = nums.length - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, use Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.dart
/* Binary search for insertion point (with duplicate elements) */\nint binarySearchInsertion(List<int> nums, int target) {\n  int i = 0, j = nums.length - 1; // Initialize closed interval [0, n-1]\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      i = m + 1; // target is in the interval [m+1, j]\n    } else if (nums[m] > target) {\n      j = m - 1; // target is in the interval [i, m-1]\n    } else {\n      j = m - 1; // The first element less than target is in the interval [i, m-1]\n    }\n  }\n  // Return insertion point i\n  return i;\n}\n
binary_search_insertion.rs
/* Binary search for insertion point (with duplicate elements) */\npub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 {\n    let (mut i, mut j) = (0, nums.len() as i32 - 1); // Initialize closed interval [0, n-1]\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if nums[m as usize] > target {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    i\n}\n
binary_search_insertion.c
/* Binary search for insertion point (with duplicate elements) */\nint binarySearchInsertion(int *nums, int numSize, int target) {\n    int i = 0, j = numSize - 1; // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1; // target is in the interval [i, m-1]\n        } else {\n            j = m - 1; // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
binary_search_insertion.kt
/* Binary search for insertion point (with duplicate elements) */\nfun binarySearchInsertion(nums: IntArray, target: Int): Int {\n    var i = 0\n    var j = nums.size - 1 // Initialize closed interval [0, n-1]\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1 // target is in the interval [m+1, j]\n        } else if (nums[m] > target) {\n            j = m - 1 // target is in the interval [i, m-1]\n        } else {\n            j = m - 1 // The first element less than target is in the interval [i, m-1]\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
binary_search_insertion.rb
### Binary search insertion point (with duplicates) ###\ndef binary_search_insertion(nums, target)\n  # Initialize closed interval [0, n-1]\n  i, j = 0, nums.length - 1\n\n  while i <= j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # target is in the interval [m+1, j]\n    elsif nums[m] > target\n      j = m - 1 # target is in the interval [i, m-1]\n    else\n      j = m - 1 # The first element less than target is in the interval [i, m-1]\n    end\n  end\n\n  i # Return insertion point i\nend\n

Tip

The code in this section all uses the \"closed interval\" approach. Interested readers can implement the \"left-closed right-open\" approach themselves.

Overall, binary search is simply about setting search targets for pointers \\(i\\) and \\(j\\) separately. The target could be a specific element (such as target) or a range of elements (such as elements less than target).

Through continuous binary iterations, both pointers \\(i\\) and \\(j\\) gradually approach their preset targets. Ultimately, they either successfully find the answer or stop after crossing the boundaries.

","path":["Chapter 10. Searching","10.2   Binary Search Insertion Point"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/","level":1,"title":"10.4   Hash Optimization Strategy","text":"

In algorithm problems, we often reduce the time complexity of algorithms by replacing linear search with hash-based search. Let's use an algorithm problem to deepen our understanding.

Question

Given an integer array nums and a target element target, search for two elements in the array whose \"sum\" equals target, and return their array indices. Any solution will do.

","path":["Chapter 10. Searching","10.4   Hash Optimization Strategy"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/#1041-linear-search-trading-time-for-space","level":2,"title":"10.4.1   Linear Search: Trading Time for Space","text":"

Consider directly traversing all possible combinations. As shown in Figure 10-9, we open a two-layer loop and judge in each round whether the sum of two integers equals target. If so, return their indices.

Figure 10-9   Linear search solution for two sum

The code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby two_sum.py
def two_sum_brute_force(nums: list[int], target: int) -> list[int]:\n    \"\"\"Method 1: Brute force enumeration\"\"\"\n    # Two nested loops, time complexity is O(n^2)\n    for i in range(len(nums) - 1):\n        for j in range(i + 1, len(nums)):\n            if nums[i] + nums[j] == target:\n                return [i, j]\n    return []\n
two_sum.cpp
/* Method 1: Brute force enumeration */\nvector<int> twoSumBruteForce(vector<int> &nums, int target) {\n    int size = nums.size();\n    // Two nested loops, time complexity is O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return {i, j};\n        }\n    }\n    return {};\n}\n
two_sum.java
/* Method 1: Brute force enumeration */\nint[] twoSumBruteForce(int[] nums, int target) {\n    int size = nums.length;\n    // Two nested loops, time complexity is O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return new int[] { i, j };\n        }\n    }\n    return new int[0];\n}\n
two_sum.cs
/* Method 1: Brute force enumeration */\nint[] TwoSumBruteForce(int[] nums, int target) {\n    int size = nums.Length;\n    // Two nested loops, time complexity is O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return [i, j];\n        }\n    }\n    return [];\n}\n
two_sum.go
/* Method 1: Brute force enumeration */\nfunc twoSumBruteForce(nums []int, target int) []int {\n    size := len(nums)\n    // Two nested loops, time complexity is O(n^2)\n    for i := 0; i < size-1; i++ {\n        for j := i + 1; j < size; j++ {\n            if nums[i]+nums[j] == target {\n                return []int{i, j}\n            }\n        }\n    }\n    return nil\n}\n
two_sum.swift
/* Method 1: Brute force enumeration */\nfunc twoSumBruteForce(nums: [Int], target: Int) -> [Int] {\n    // Two nested loops, time complexity is O(n^2)\n    for i in nums.indices.dropLast() {\n        for j in nums.indices.dropFirst(i + 1) {\n            if nums[i] + nums[j] == target {\n                return [i, j]\n            }\n        }\n    }\n    return [0]\n}\n
two_sum.js
/* Method 1: Brute force enumeration */\nfunction twoSumBruteForce(nums, target) {\n    const n = nums.length;\n    // Two nested loops, time complexity is O(n^2)\n    for (let i = 0; i < n; i++) {\n        for (let j = i + 1; j < n; j++) {\n            if (nums[i] + nums[j] === target) {\n                return [i, j];\n            }\n        }\n    }\n    return [];\n}\n
two_sum.ts
/* Method 1: Brute force enumeration */\nfunction twoSumBruteForce(nums: number[], target: number): number[] {\n    const n = nums.length;\n    // Two nested loops, time complexity is O(n^2)\n    for (let i = 0; i < n; i++) {\n        for (let j = i + 1; j < n; j++) {\n            if (nums[i] + nums[j] === target) {\n                return [i, j];\n            }\n        }\n    }\n    return [];\n}\n
two_sum.dart
/* Method 1: Brute force enumeration */\nList<int> twoSumBruteForce(List<int> nums, int target) {\n  int size = nums.length;\n  // Two nested loops, time complexity is O(n^2)\n  for (var i = 0; i < size - 1; i++) {\n    for (var j = i + 1; j < size; j++) {\n      if (nums[i] + nums[j] == target) return [i, j];\n    }\n  }\n  return [0];\n}\n
two_sum.rs
/* Method 1: Brute force enumeration */\npub fn two_sum_brute_force(nums: &Vec<i32>, target: i32) -> Option<Vec<i32>> {\n    let size = nums.len();\n    // Two nested loops, time complexity is O(n^2)\n    for i in 0..size - 1 {\n        for j in i + 1..size {\n            if nums[i] + nums[j] == target {\n                return Some(vec![i as i32, j as i32]);\n            }\n        }\n    }\n    None\n}\n
two_sum.c
/* Method 1: Brute force enumeration */\nint *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) {\n    for (int i = 0; i < numsSize; ++i) {\n        for (int j = i + 1; j < numsSize; ++j) {\n            if (nums[i] + nums[j] == target) {\n                int *res = malloc(sizeof(int) * 2);\n                res[0] = i, res[1] = j;\n                *returnSize = 2;\n                return res;\n            }\n        }\n    }\n    *returnSize = 0;\n    return NULL;\n}\n
two_sum.kt
/* Method 1: Brute force enumeration */\nfun twoSumBruteForce(nums: IntArray, target: Int): IntArray {\n    val size = nums.size\n    // Two nested loops, time complexity is O(n^2)\n    for (i in 0..<size - 1) {\n        for (j in i + 1..<size) {\n            if (nums[i] + nums[j] == target) return intArrayOf(i, j)\n        }\n    }\n    return IntArray(0)\n}\n
two_sum.rb
### Method 1: Brute force enumeration ###\ndef two_sum_brute_force(nums, target)\n  # Two nested loops, time complexity is O(n^2)\n  for i in 0...(nums.length - 1)\n    for j in (i + 1)...nums.length\n      return [i, j] if nums[i] + nums[j] == target\n    end\n  end\n\n  []\nend\n

This method has a time complexity of \\(O(n^2)\\) and a space complexity of \\(O(1)\\), which is very time-consuming with large data volumes.

","path":["Chapter 10. Searching","10.4   Hash Optimization Strategy"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/#1042-hash-based-search-trading-space-for-time","level":2,"title":"10.4.2   Hash-Based Search: Trading Space for Time","text":"

Consider using a hash table where key-value pairs are array elements and element indices respectively. Loop through the array, performing the steps shown in Figure 10-10 in each round:

  1. Check if the number target - nums[i] is in the hash table. If so, directly return the indices of these two elements.
  2. Add the key-value pair nums[i] and index i to the hash table.
<1><2><3>

Figure 10-10   Hash table solution for two sum

The implementation code is shown below, requiring only a single loop:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby two_sum.py
def two_sum_hash_table(nums: list[int], target: int) -> list[int]:\n    \"\"\"Method 2: Auxiliary hash table\"\"\"\n    # Auxiliary hash table, space complexity is O(n)\n    dic = {}\n    # Single loop, time complexity is O(n)\n    for i in range(len(nums)):\n        if target - nums[i] in dic:\n            return [dic[target - nums[i]], i]\n        dic[nums[i]] = i\n    return []\n
two_sum.cpp
/* Method 2: Auxiliary hash table */\nvector<int> twoSumHashTable(vector<int> &nums, int target) {\n    int size = nums.size();\n    // Auxiliary hash table, space complexity is O(n)\n    unordered_map<int, int> dic;\n    // Single loop, time complexity is O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.find(target - nums[i]) != dic.end()) {\n            return {dic[target - nums[i]], i};\n        }\n        dic.emplace(nums[i], i);\n    }\n    return {};\n}\n
two_sum.java
/* Method 2: Auxiliary hash table */\nint[] twoSumHashTable(int[] nums, int target) {\n    int size = nums.length;\n    // Auxiliary hash table, space complexity is O(n)\n    Map<Integer, Integer> dic = new HashMap<>();\n    // Single loop, time complexity is O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.containsKey(target - nums[i])) {\n            return new int[] { dic.get(target - nums[i]), i };\n        }\n        dic.put(nums[i], i);\n    }\n    return new int[0];\n}\n
two_sum.cs
/* Method 2: Auxiliary hash table */\nint[] TwoSumHashTable(int[] nums, int target) {\n    int size = nums.Length;\n    // Auxiliary hash table, space complexity is O(n)\n    Dictionary<int, int> dic = [];\n    // Single loop, time complexity is O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.ContainsKey(target - nums[i])) {\n            return [dic[target - nums[i]], i];\n        }\n        dic.Add(nums[i], i);\n    }\n    return [];\n}\n
two_sum.go
/* Method 2: Auxiliary hash table */\nfunc twoSumHashTable(nums []int, target int) []int {\n    // Auxiliary hash table, space complexity is O(n)\n    hashTable := map[int]int{}\n    // Single loop, time complexity is O(n)\n    for idx, val := range nums {\n        if preIdx, ok := hashTable[target-val]; ok {\n            return []int{preIdx, idx}\n        }\n        hashTable[val] = idx\n    }\n    return nil\n}\n
two_sum.swift
/* Method 2: Auxiliary hash table */\nfunc twoSumHashTable(nums: [Int], target: Int) -> [Int] {\n    // Auxiliary hash table, space complexity is O(n)\n    var dic: [Int: Int] = [:]\n    // Single loop, time complexity is O(n)\n    for i in nums.indices {\n        if let j = dic[target - nums[i]] {\n            return [j, i]\n        }\n        dic[nums[i]] = i\n    }\n    return [0]\n}\n
two_sum.js
/* Method 2: Auxiliary hash table */\nfunction twoSumHashTable(nums, target) {\n    // Auxiliary hash table, space complexity is O(n)\n    let m = {};\n    // Single loop, time complexity is O(n)\n    for (let i = 0; i < nums.length; i++) {\n        if (m[target - nums[i]] !== undefined) {\n            return [m[target - nums[i]], i];\n        } else {\n            m[nums[i]] = i;\n        }\n    }\n    return [];\n}\n
two_sum.ts
/* Method 2: Auxiliary hash table */\nfunction twoSumHashTable(nums: number[], target: number): number[] {\n    // Auxiliary hash table, space complexity is O(n)\n    let m: Map<number, number> = new Map();\n    // Single loop, time complexity is O(n)\n    for (let i = 0; i < nums.length; i++) {\n        let index = m.get(target - nums[i]);\n        if (index !== undefined) {\n            return [index, i];\n        } else {\n            m.set(nums[i], i);\n        }\n    }\n    return [];\n}\n
two_sum.dart
/* Method 2: Auxiliary hash table */\nList<int> twoSumHashTable(List<int> nums, int target) {\n  int size = nums.length;\n  // Auxiliary hash table, space complexity is O(n)\n  Map<int, int> dic = HashMap();\n  // Single loop, time complexity is O(n)\n  for (var i = 0; i < size; i++) {\n    if (dic.containsKey(target - nums[i])) {\n      return [dic[target - nums[i]]!, i];\n    }\n    dic.putIfAbsent(nums[i], () => i);\n  }\n  return [0];\n}\n
two_sum.rs
/* Method 2: Auxiliary hash table */\npub fn two_sum_hash_table(nums: &Vec<i32>, target: i32) -> Option<Vec<i32>> {\n    // Auxiliary hash table, space complexity is O(n)\n    let mut dic = HashMap::new();\n    // Single loop, time complexity is O(n)\n    for (i, num) in nums.iter().enumerate() {\n        match dic.get(&(target - num)) {\n            Some(v) => return Some(vec![*v as i32, i as i32]),\n            None => dic.insert(num, i as i32),\n        };\n    }\n    None\n}\n
two_sum.c
/* Hash table */\ntypedef struct {\n    int key;\n    int val;\n    UT_hash_handle hh; // Implemented using uthash.h\n} HashTable;\n\n/* Hash table lookup */\nHashTable *find(HashTable *h, int key) {\n    HashTable *tmp;\n    HASH_FIND_INT(h, &key, tmp);\n    return tmp;\n}\n\n/* Hash table element insertion */\nvoid insert(HashTable **h, int key, int val) {\n    HashTable *t = find(*h, key);\n    if (t == NULL) {\n        HashTable *tmp = malloc(sizeof(HashTable));\n        tmp->key = key, tmp->val = val;\n        HASH_ADD_INT(*h, key, tmp);\n    } else {\n        t->val = val;\n    }\n}\n\n/* Method 2: Auxiliary hash table */\nint *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) {\n    HashTable *hashtable = NULL;\n    for (int i = 0; i < numsSize; i++) {\n        HashTable *t = find(hashtable, target - nums[i]);\n        if (t != NULL) {\n            int *res = malloc(sizeof(int) * 2);\n            res[0] = t->val, res[1] = i;\n            *returnSize = 2;\n            return res;\n        }\n        insert(&hashtable, nums[i], i);\n    }\n    *returnSize = 0;\n    return NULL;\n}\n
two_sum.kt
/* Method 2: Auxiliary hash table */\nfun twoSumHashTable(nums: IntArray, target: Int): IntArray {\n    val size = nums.size\n    // Auxiliary hash table, space complexity is O(n)\n    val dic = HashMap<Int, Int>()\n    // Single loop, time complexity is O(n)\n    for (i in 0..<size) {\n        if (dic.containsKey(target - nums[i])) {\n            return intArrayOf(dic[target - nums[i]]!!, i)\n        }\n        dic[nums[i]] = i\n    }\n    return IntArray(0)\n}\n
two_sum.rb
### Method 2: Auxiliary hash table ###\ndef two_sum_hash_table(nums, target)\n  # Auxiliary hash table, space complexity is O(n)\n  dic = {}\n  # Single loop, time complexity is O(n)\n  for i in 0...nums.length\n    return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i])\n\n    dic[nums[i]] = i\n  end\n\n  []\nend\n

This method reduces the time complexity from \\(O(n^2)\\) to \\(O(n)\\) through hash-based search, greatly improving runtime efficiency.

Since an additional hash table needs to be maintained, the space complexity is \\(O(n)\\). Nevertheless, this method achieves a more balanced overall time-space efficiency, making it the optimal solution for this problem.

","path":["Chapter 10. Searching","10.4   Hash Optimization Strategy"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/","level":1,"title":"10.5   Searching Algorithms Revisited","text":"

Searching algorithms are used to search for one or a group of elements that meet specific conditions in data structures (such as arrays, linked lists, trees, or graphs).

Searching algorithms can be divided into the following two categories based on their implementation approach:

  • Locating target elements by traversing the data structure, such as traversing arrays, linked lists, trees, and graphs.
  • Achieving efficient element search by utilizing data organization structure or prior information contained in the data, such as binary search, hash-based search, and binary search tree search.

It's not hard to see that these topics have all been covered in previous chapters, so searching algorithms are not unfamiliar to us. In this section, we will approach from a more systematic perspective and re-examine searching algorithms.

","path":["Chapter 10. Searching","10.5   Searching Algorithms Revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1051-brute-force-search","level":2,"title":"10.5.1   Brute-Force Search","text":"

Brute-force search locates target elements by traversing each element of the data structure.

  • \"Linear search\" is applicable to linear data structures such as arrays and linked lists. It starts from one end of the data structure and accesses elements one by one until the target element is found or the other end is reached without finding the target element.
  • \"Breadth-first search\" and \"depth-first search\" are two traversal strategies for graphs and trees. Breadth-first search starts from the initial node and searches layer by layer, visiting nodes from near to far. Depth-first search starts from the initial node, follows a path to the end, then backtracks and tries other paths until the entire data structure is traversed.

The advantage of brute-force search is that it is simple and has good generality, requiring no data preprocessing or additional data structures.

However, the time complexity of such algorithms is \\(O(n)\\), where \\(n\\) is the number of elements, so performance is poor when dealing with large amounts of data.

","path":["Chapter 10. Searching","10.5   Searching Algorithms Revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1052-adaptive-search","level":2,"title":"10.5.2   Adaptive Search","text":"

Adaptive search utilizes the unique properties of data (such as orderliness) to optimize the search process, thereby locating target elements more efficiently.

  • \"Binary search\" uses the orderliness of data to achieve efficient searching, applicable only to arrays.
  • \"Hash-based search\" uses hash tables to establish key-value pair mappings between search data and target data, thereby achieving query operations.
  • \"Tree search\" in specific tree structures (such as binary search trees), quickly eliminates nodes based on comparing node values to locate target elements.

The advantage of such algorithms is high efficiency, with time complexity reaching \\(O(\\log n)\\) or even \\(O(1)\\).

However, using these algorithms often requires data preprocessing. For example, binary search requires pre-sorting the array, while hash-based search and tree search both require additional data structures, and maintaining these data structures also requires extra time and space overhead.

Tip

Adaptive search algorithms are often called lookup algorithms, mainly used to quickly retrieve target elements in specific data structures.

","path":["Chapter 10. Searching","10.5   Searching Algorithms Revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1053-search-method-selection","level":2,"title":"10.5.3   Search Method Selection","text":"

Given a dataset of size \\(n\\), we can use linear search, binary search, tree search, hash-based search, and other methods to search for the target element. The working principles of each method are shown in Figure 10-11.

Figure 10-11   Multiple search strategies

The operational efficiency and characteristics of the above methods are as follows:

Table 10-1   Comparison of search algorithm efficiency

Linear search Binary search Tree search Hash-based search Search element \\(O(n)\\) \\(O(\\log n)\\) \\(O(\\log n)\\) \\(O(1)\\) Insert element \\(O(1)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\) Delete element \\(O(n)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\) Extra space \\(O(1)\\) \\(O(1)\\) \\(O(n)\\) \\(O(n)\\) Data preprocessing / Sorting \\(O(n \\log n)\\) Tree building \\(O(n \\log n)\\) Hash table building \\(O(n)\\) Data ordered Unordered Ordered Ordered Unordered

The choice of search algorithm also depends on data volume, search performance requirements, data query and update frequency, etc.

Linear search

  • Good generality, requiring no data preprocessing operations. If we only need to query the data once, the data preprocessing time for the other three methods would be longer than linear search.
  • Suitable for small data volumes, where time complexity has less impact on efficiency.
  • Suitable for scenarios with high data update frequency, as this method does not require any additional data maintenance.

Binary search

  • Suitable for large data volumes with stable efficiency performance, worst-case time complexity of \\(O(\\log n)\\).
  • Data volume cannot be too large, as storing arrays requires contiguous memory space.
  • Not suitable for scenarios with frequent data insertion and deletion, as maintaining a sorted array has high overhead.

Hash-based search

  • Suitable for scenarios with high query performance requirements, with an average time complexity of \\(O(1)\\).
  • Not suitable for scenarios requiring ordered data or range searches, as hash tables cannot maintain data orderliness.
  • High dependence on hash functions and hash collision handling strategies, with significant risk of performance degradation.
  • Not suitable for excessively large data volumes, as hash tables require extra space to minimize collisions and thus provide good query performance.

Tree search

  • Suitable for massive data, as tree nodes are stored dispersedly in memory.
  • Suitable for scenarios requiring maintained ordered data or range searches.
  • During continuous node insertion and deletion, binary search trees may become skewed, degrading time complexity to \\(O(n)\\).
  • If using AVL trees or red-black trees, all operations can run stably at \\(O(\\log n)\\) efficiency, but operations to maintain tree balance add extra overhead.
","path":["Chapter 10. Searching","10.5   Searching Algorithms Revisited"],"tags":[]},{"location":"chapter_searching/summary/","level":1,"title":"10.6   Summary","text":"","path":["Chapter 10. Searching","10.6   Summary"],"tags":[]},{"location":"chapter_searching/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Binary search relies on data orderliness and progressively reduces the search interval by half through loops. It requires input data to be sorted and is only applicable to arrays or data structures based on array implementations.
  • Brute-force search locates data by traversing the data structure. Linear search is applicable to arrays and linked lists, while breadth-first search and depth-first search are applicable to graphs and trees. Such algorithms have good generality and require no data preprocessing, but have a relatively high time complexity of \\(O(n)\\).
  • Hash-based search, tree search, and binary search are efficient search methods that can quickly locate target elements in specific data structures. Such algorithms are highly efficient with time complexity reaching \\(O(\\log n)\\) or even \\(O(1)\\), but typically require additional data structures.
  • In practice, we need to analyze factors such as data scale, search performance requirements, and data query and update frequency to choose the appropriate search method.
  • Linear search is suitable for small-scale or frequently updated data; binary search is suitable for large-scale, sorted data; hash-based search is suitable for data with high query efficiency requirements and no need for range queries; tree search is suitable for large-scale dynamic data that needs to maintain order and support range queries.
  • Replacing linear search with hash-based search is a commonly used strategy to optimize runtime, reducing time complexity from \\(O(n)\\) to \\(O(1)\\).
","path":["Chapter 10. Searching","10.6   Summary"],"tags":[]},{"location":"chapter_sorting/","level":1,"title":"Chapter 11.   Sorting","text":"

Abstract

Sorting is like a magic key that transforms chaos into order, enabling us to understand and process data more efficiently.

Whether it's simple ascending order or complex categorized arrangements, sorting demonstrates the harmonious beauty of data.

","path":["Chapter 11. Sorting","Chapter 11.   Sorting"],"tags":[]},{"location":"chapter_sorting/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 11.1   Sorting Algorithms
  • 11.2   Selection Sort
  • 11.3   Bubble Sort
  • 11.4   Insertion Sort
  • 11.5   Quick Sort
  • 11.6   Merge Sort
  • 11.7   Heap Sort
  • 11.8   Bucket Sort
  • 11.9   Counting Sort
  • 11.10   Radix Sort
  • 11.11   Summary
","path":["Chapter 11. Sorting","Chapter 11.   Sorting"],"tags":[]},{"location":"chapter_sorting/bubble_sort/","level":1,"title":"11.3   Bubble Sort","text":"

Bubble sort (bubble sort) achieves sorting by continuously comparing and swapping adjacent elements. This process is like bubbles rising from the bottom to the top, hence the name bubble sort.

As shown in Figure 11-4, the bubbling process can be simulated using element swap operations: starting from the leftmost end of the array and traversing to the right, compare the size of adjacent elements, and if \"left element > right element\", swap them. After completing the traversal, the largest element will be moved to the rightmost end of the array.

<1><2><3><4><5><6><7>

Figure 11-4   Simulating bubble using element swap operation

","path":["Chapter 11. Sorting","11.3   Bubble Sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1131-algorithm-flow","level":2,"title":"11.3.1   Algorithm Flow","text":"

Assume the array has length \\(n\\). The steps of bubble sort are shown in Figure 11-5.

  1. First, perform \"bubbling\" on \\(n\\) elements, swapping the largest element of the array to its correct position.
  2. Next, perform \"bubbling\" on the remaining \\(n - 1\\) elements, swapping the second largest element to its correct position.
  3. And so on. After \\(n - 1\\) rounds of \"bubbling\", the largest \\(n - 1\\) elements have all been swapped to their correct positions.
  4. The only remaining element must be the smallest element, requiring no sorting, so the array sorting is complete.

Figure 11-5   Bubble sort flow

Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bubble_sort.py
def bubble_sort(nums: list[int]):\n    \"\"\"Bubble sort\"\"\"\n    n = len(nums)\n    # Outer loop: unsorted interval is [0, i]\n    for i in range(n - 1, 0, -1):\n        # Inner loop: swap the largest element in the unsorted interval [0, i] to the rightmost end of the interval\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                nums[j], nums[j + 1] = nums[j + 1], nums[j]\n
bubble_sort.cpp
/* Bubble sort */\nvoid bubbleSort(vector<int> &nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                // Using std::swap() function here\n                swap(nums[j], nums[j + 1]);\n            }\n        }\n    }\n}\n
bubble_sort.java
/* Bubble sort */\nvoid bubbleSort(int[] nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
bubble_sort.cs
/* Bubble sort */\nvoid BubbleSort(int[] nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n            }\n        }\n    }\n}\n
bubble_sort.go
/* Bubble sort */\nfunc bubbleSort(nums []int) {\n    // Outer loop: unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                nums[j], nums[j+1] = nums[j+1], nums[j]\n            }\n        }\n    }\n}\n
bubble_sort.swift
/* Bubble sort */\nfunc bubbleSort(nums: inout [Int]) {\n    // Outer loop: unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swapAt(j, j + 1)\n            }\n        }\n    }\n}\n
bubble_sort.js
/* Bubble sort */\nfunction bubbleSort(nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
bubble_sort.ts
/* Bubble sort */\nfunction bubbleSort(nums: number[]): void {\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
bubble_sort.dart
/* Bubble sort */\nvoid bubbleSort(List<int> nums) {\n  // Outer loop: unsorted range is [0, i]\n  for (int i = nums.length - 1; i > 0; i--) {\n    // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for (int j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n      }\n    }\n  }\n}\n
bubble_sort.rs
/* Bubble sort */\nfn bubble_sort(nums: &mut [i32]) {\n    // Outer loop: unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swap(j, j + 1);\n            }\n        }\n    }\n}\n
bubble_sort.c
/* Bubble sort */\nvoid bubbleSort(int nums[], int size) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = size - 1; i > 0; i--) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                int temp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = temp;\n            }\n        }\n    }\n}\n
bubble_sort.kt
/* Bubble sort */\nfun bubbleSort(nums: IntArray) {\n    // Outer loop: unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n            }\n        }\n    }\n}\n
bubble_sort.rb
### Bubble sort ###\ndef bubble_sort(nums)\n  n = nums.length\n  # Outer loop: unsorted range is [0, i]\n  for i in (n - 1).downto(1)\n    # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        nums[j], nums[j + 1] = nums[j + 1], nums[j]\n      end\n    end\n  end\nend\n
","path":["Chapter 11. Sorting","11.3   Bubble Sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1132-efficiency-optimization","level":2,"title":"11.3.2   Efficiency Optimization","text":"

We notice that if no swap operations are performed during a certain round of \"bubbling\", it means the array has already completed sorting and can directly return the result. Therefore, we can add a flag flag to monitor this situation and return immediately once it occurs.

After optimization, the worst-case time complexity and average time complexity of bubble sort remain \\(O(n^2)\\); but when the input array is completely ordered, the best-case time complexity can reach \\(O(n)\\).

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bubble_sort.py
def bubble_sort_with_flag(nums: list[int]):\n    \"\"\"Bubble sort (flag optimization)\"\"\"\n    n = len(nums)\n    # Outer loop: unsorted interval is [0, i]\n    for i in range(n - 1, 0, -1):\n        flag = False  # Initialize flag\n        # Inner loop: swap the largest element in the unsorted interval [0, i] to the rightmost end of the interval\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                nums[j], nums[j + 1] = nums[j + 1], nums[j]\n                flag = True  # Record element swap\n        if not flag:\n            break  # No elements were swapped in this round of \"bubbling\", exit directly\n
bubble_sort.cpp
/* Bubble sort (flag optimization)*/\nvoid bubbleSortWithFlag(vector<int> &nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        bool flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                // Using std::swap() function here\n                swap(nums[j], nums[j + 1]);\n                flag = true; // Record element swap\n            }\n        }\n        if (!flag)\n            break; // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.java
/* Bubble sort (flag optimization) */\nvoid bubbleSortWithFlag(int[] nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        boolean flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record element swap\n            }\n        }\n        if (!flag)\n            break; // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.cs
/* Bubble sort (flag optimization) */\nvoid BubbleSortWithFlag(int[] nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        bool flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n                flag = true;  // Record element swap\n            }\n        }\n        if (!flag) break;     // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.go
/* Bubble sort (flag optimization) */\nfunc bubbleSortWithFlag(nums []int) {\n    // Outer loop: unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        flag := false // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                nums[j], nums[j+1] = nums[j+1], nums[j]\n                flag = true // Record element swap\n            }\n        }\n        if flag == false { // No elements were swapped in this round of \"bubbling\", exit directly\n            break\n        }\n    }\n}\n
bubble_sort.swift
/* Bubble sort (flag optimization) */\nfunc bubbleSortWithFlag(nums: inout [Int]) {\n    // Outer loop: unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        var flag = false // Initialize flag\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swapAt(j, j + 1)\n                flag = true // Record element swap\n            }\n        }\n        if !flag { // No elements were swapped in this round of \"bubbling\", exit directly\n            break\n        }\n    }\n}\n
bubble_sort.js
/* Bubble sort (flag optimization) */\nfunction bubbleSortWithFlag(nums) {\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        let flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record element swap\n            }\n        }\n        if (!flag) break; // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.ts
/* Bubble sort (flag optimization) */\nfunction bubbleSortWithFlag(nums: number[]): void {\n    // Outer loop: unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        let flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record element swap\n            }\n        }\n        if (!flag) break; // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.dart
/* Bubble sort (flag optimization) */\nvoid bubbleSortWithFlag(List<int> nums) {\n  // Outer loop: unsorted range is [0, i]\n  for (int i = nums.length - 1; i > 0; i--) {\n    bool flag = false; // Initialize flag\n    // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for (int j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n        flag = true; // Record element swap\n      }\n    }\n    if (!flag) break; // No elements were swapped in this round of \"bubbling\", exit directly\n  }\n}\n
bubble_sort.rs
/* Bubble sort (flag optimization) */\nfn bubble_sort_with_flag(nums: &mut [i32]) {\n    // Outer loop: unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        let mut flag = false; // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swap(j, j + 1);\n                flag = true; // Record element swap\n            }\n        }\n        if !flag {\n            break; // No elements were swapped in this round of \"bubbling\", exit directly\n        };\n    }\n}\n
bubble_sort.c
/* Bubble sort (flag optimization) */\nvoid bubbleSortWithFlag(int nums[], int size) {\n    // Outer loop: unsorted range is [0, i]\n    for (int i = size - 1; i > 0; i--) {\n        bool flag = false;\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                int temp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = temp;\n                flag = true;\n            }\n        }\n        if (!flag)\n            break;\n    }\n}\n
bubble_sort.kt
/* Bubble sort (flag optimization) */\nfun bubbleSortWithFlag(nums: IntArray) {\n    // Outer loop: unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        var flag = false // Initialize flag\n        // Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n                flag = true // Record element swap\n            }\n        }\n        if (!flag) break // No elements were swapped in this round of \"bubbling\", exit directly\n    }\n}\n
bubble_sort.rb
### Bubble sort (flag optimization) ###\ndef bubble_sort_with_flag(nums)\n  n = nums.length\n  # Outer loop: unsorted range is [0, i]\n  for i in (n - 1).downto(1)\n    flag = false # Initialize flag\n\n    # Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        nums[j], nums[j + 1] = nums[j + 1], nums[j]\n        flag = true # Record element swap\n      end\n    end\n\n    break unless flag # No elements were swapped in this round of \"bubbling\", exit directly\n  end\nend\n
","path":["Chapter 11. Sorting","11.3   Bubble Sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1133-algorithm-characteristics","level":2,"title":"11.3.3   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n^2)\\), adaptive sorting: The array lengths traversed in each round of \"bubbling\" are \\(n - 1\\), \\(n - 2\\), \\(\\dots\\), \\(2\\), \\(1\\), totaling \\((n - 1) n / 2\\). After introducing the flag optimization, the best-case time complexity can reach \\(O(n)\\).
  • Space complexity of \\(O(1)\\), in-place sorting: Pointers \\(i\\) and \\(j\\) use a constant amount of extra space.
  • Stable sorting: Since equal elements are not swapped during \"bubbling\".
","path":["Chapter 11. Sorting","11.3   Bubble Sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/","level":1,"title":"11.8   Bucket Sort","text":"

The several sorting algorithms mentioned earlier all belong to \"comparison-based sorting algorithms\", which achieve sorting by comparing the size of elements. The time complexity of such sorting algorithms cannot exceed \\(O(n \\log n)\\). Next, we will explore several \"non-comparison sorting algorithms\", whose time complexity can reach linear order.

Bucket sort (bucket sort) is a typical application of the divide-and-conquer strategy. It works by setting up buckets with size order, each bucket corresponding to a data range, evenly distributing data to each bucket; then, sorting within each bucket separately; finally, merging all data in the order of the buckets.

","path":["Chapter 11. Sorting","11.8   Bucket Sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1181-algorithm-flow","level":2,"title":"11.8.1   Algorithm Flow","text":"

Consider an array of length \\(n\\), whose elements are floating-point numbers in the range \\([0, 1)\\). The flow of bucket sort is shown in Figure 11-13.

  1. Initialize \\(k\\) buckets and distribute the \\(n\\) elements into the \\(k\\) buckets.
  2. Sort each bucket separately (here we use the built-in sorting function of the programming language).
  3. Merge the results in order from smallest to largest bucket.

Figure 11-13   Bucket sort algorithm flow

The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bucket_sort.py
def bucket_sort(nums: list[float]):\n    \"\"\"Bucket sort\"\"\"\n    # Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    k = len(nums) // 2\n    buckets = [[] for _ in range(k)]\n    # 1. Distribute array elements into various buckets\n    for num in nums:\n        # Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        i = int(num * k)\n        # Add num to bucket i\n        buckets[i].append(num)\n    # 2. Sort each bucket\n    for bucket in buckets:\n        # Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.sort()\n    # 3. Traverse buckets to merge results\n    i = 0\n    for bucket in buckets:\n        for num in bucket:\n            nums[i] = num\n            i += 1\n
bucket_sort.cpp
/* Bucket sort */\nvoid bucketSort(vector<float> &nums) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    int k = nums.size() / 2;\n    vector<vector<float>> buckets(k);\n    // 1. Distribute array elements into various buckets\n    for (float num : nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        int i = num * k;\n        // Add num to bucket bucket_idx\n        buckets[i].push_back(num);\n    }\n    // 2. Sort each bucket\n    for (vector<float> &bucket : buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        sort(bucket.begin(), bucket.end());\n    }\n    // 3. Traverse buckets to merge results\n    int i = 0;\n    for (vector<float> &bucket : buckets) {\n        for (float num : bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
bucket_sort.java
/* Bucket sort */\nvoid bucketSort(float[] nums) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    int k = nums.length / 2;\n    List<List<Float>> buckets = new ArrayList<>();\n    for (int i = 0; i < k; i++) {\n        buckets.add(new ArrayList<>());\n    }\n    // 1. Distribute array elements into various buckets\n    for (float num : nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        int i = (int) (num * k);\n        // Add num to bucket i\n        buckets.get(i).add(num);\n    }\n    // 2. Sort each bucket\n    for (List<Float> bucket : buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        Collections.sort(bucket);\n    }\n    // 3. Traverse buckets to merge results\n    int i = 0;\n    for (List<Float> bucket : buckets) {\n        for (float num : bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
bucket_sort.cs
/* Bucket sort */\nvoid BucketSort(float[] nums) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    int k = nums.Length / 2;\n    List<List<float>> buckets = [];\n    for (int i = 0; i < k; i++) {\n        buckets.Add([]);\n    }\n    // 1. Distribute array elements into various buckets\n    foreach (float num in nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        int i = (int)(num * k);\n        // Add num to bucket i\n        buckets[i].Add(num);\n    }\n    // 2. Sort each bucket\n    foreach (List<float> bucket in buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.Sort();\n    }\n    // 3. Traverse buckets to merge results\n    int j = 0;\n    foreach (List<float> bucket in buckets) {\n        foreach (float num in bucket) {\n            nums[j++] = num;\n        }\n    }\n}\n
bucket_sort.go
/* Bucket sort */\nfunc bucketSort(nums []float64) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    k := len(nums) / 2\n    buckets := make([][]float64, k)\n    for i := 0; i < k; i++ {\n        buckets[i] = make([]float64, 0)\n    }\n    // 1. Distribute array elements into various buckets\n    for _, num := range nums {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        i := int(num * float64(k))\n        // Add num to bucket i\n        buckets[i] = append(buckets[i], num)\n    }\n    // 2. Sort each bucket\n    for i := 0; i < k; i++ {\n        // Use built-in slice sorting function, can also be replaced with other sorting algorithms\n        sort.Float64s(buckets[i])\n    }\n    // 3. Traverse buckets to merge results\n    i := 0\n    for _, bucket := range buckets {\n        for _, num := range bucket {\n            nums[i] = num\n            i++\n        }\n    }\n}\n
bucket_sort.swift
/* Bucket sort */\nfunc bucketSort(nums: inout [Double]) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    let k = nums.count / 2\n    var buckets = (0 ..< k).map { _ in [Double]() }\n    // 1. Distribute array elements into various buckets\n    for num in nums {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        let i = Int(num * Double(k))\n        // Add num to bucket i\n        buckets[i].append(num)\n    }\n    // 2. Sort each bucket\n    for i in buckets.indices {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        buckets[i].sort()\n    }\n    // 3. Traverse buckets to merge results\n    var i = nums.startIndex\n    for bucket in buckets {\n        for num in bucket {\n            nums[i] = num\n            i += 1\n        }\n    }\n}\n
bucket_sort.js
/* Bucket sort */\nfunction bucketSort(nums) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    const k = nums.length / 2;\n    const buckets = [];\n    for (let i = 0; i < k; i++) {\n        buckets.push([]);\n    }\n    // 1. Distribute array elements into various buckets\n    for (const num of nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        const i = Math.floor(num * k);\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for (const bucket of buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.sort((a, b) => a - b);\n    }\n    // 3. Traverse buckets to merge results\n    let i = 0;\n    for (const bucket of buckets) {\n        for (const num of bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
bucket_sort.ts
/* Bucket sort */\nfunction bucketSort(nums: number[]): void {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    const k = nums.length / 2;\n    const buckets: number[][] = [];\n    for (let i = 0; i < k; i++) {\n        buckets.push([]);\n    }\n    // 1. Distribute array elements into various buckets\n    for (const num of nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        const i = Math.floor(num * k);\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for (const bucket of buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.sort((a, b) => a - b);\n    }\n    // 3. Traverse buckets to merge results\n    let i = 0;\n    for (const bucket of buckets) {\n        for (const num of bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
bucket_sort.dart
/* Bucket sort */\nvoid bucketSort(List<double> nums) {\n  // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n  int k = nums.length ~/ 2;\n  List<List<double>> buckets = List.generate(k, (index) => []);\n\n  // 1. Distribute array elements into various buckets\n  for (double _num in nums) {\n    // Input data range is [0, 1), use _num * k to map to index range [0, k-1]\n    int i = (_num * k).toInt();\n    // Add _num to bucket bucket_idx\n    buckets[i].add(_num);\n  }\n  // 2. Sort each bucket\n  for (List<double> bucket in buckets) {\n    bucket.sort();\n  }\n  // 3. Traverse buckets to merge results\n  int i = 0;\n  for (List<double> bucket in buckets) {\n    for (double _num in bucket) {\n      nums[i++] = _num;\n    }\n  }\n}\n
bucket_sort.rs
/* Bucket sort */\nfn bucket_sort(nums: &mut [f64]) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    let k = nums.len() / 2;\n    let mut buckets = vec![vec![]; k];\n    // 1. Distribute array elements into various buckets\n    for &num in nums.iter() {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        let i = (num * k as f64) as usize;\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for bucket in &mut buckets {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.sort_by(|a, b| a.partial_cmp(b).unwrap());\n    }\n    // 3. Traverse buckets to merge results\n    let mut i = 0;\n    for bucket in buckets.iter() {\n        for &num in bucket.iter() {\n            nums[i] = num;\n            i += 1;\n        }\n    }\n}\n
bucket_sort.c
/* Bucket sort */\nvoid bucketSort(float nums[], int n) {\n    int k = n / 2;                                 // Initialize k = n/2 buckets\n    int *sizes = malloc(k * sizeof(int));          // Record each bucket's size\n    float **buckets = malloc(k * sizeof(float *)); // Array of dynamic arrays (buckets)\n    // Pre-allocate sufficient space for each bucket\n    for (int i = 0; i < k; ++i) {\n        buckets[i] = (float *)malloc(n * sizeof(float));\n        sizes[i] = 0;\n    }\n    // 1. Distribute array elements into various buckets\n    for (int i = 0; i < n; ++i) {\n        int idx = (int)(nums[i] * k);\n        buckets[idx][sizes[idx]++] = nums[i];\n    }\n    // 2. Sort each bucket\n    for (int i = 0; i < k; ++i) {\n        qsort(buckets[i], sizes[i], sizeof(float), compare);\n    }\n    // 3. Merge sorted buckets\n    int idx = 0;\n    for (int i = 0; i < k; ++i) {\n        for (int j = 0; j < sizes[i]; ++j) {\n            nums[idx++] = buckets[i][j];\n        }\n        // Free memory\n        free(buckets[i]);\n    }\n}\n
bucket_sort.kt
/* Bucket sort */\nfun bucketSort(nums: FloatArray) {\n    // Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n    val k = nums.size / 2\n    val buckets = mutableListOf<MutableList<Float>>()\n    for (i in 0..<k) {\n        buckets.add(mutableListOf())\n    }\n    // 1. Distribute array elements into various buckets\n    for (num in nums) {\n        // Input data range is [0, 1), use num * k to map to index range [0, k-1]\n        val i = (num * k).toInt()\n        // Add num to bucket i\n        buckets[i].add(num)\n    }\n    // 2. Sort each bucket\n    for (bucket in buckets) {\n        // Use built-in sorting function, can also replace with other sorting algorithms\n        bucket.sort()\n    }\n    // 3. Traverse buckets to merge results\n    var i = 0\n    for (bucket in buckets) {\n        for (num in bucket) {\n            nums[i++] = num\n        }\n    }\n}\n
bucket_sort.rb
### Bucket sort ###\ndef bucket_sort(nums)\n  # Initialize k = n/2 buckets, expected to allocate 2 elements per bucket\n  k = nums.length / 2\n  buckets = Array.new(k) { [] }\n\n  # 1. Distribute array elements into various buckets\n  nums.each do |num|\n    # Input data range is [0, 1), use num * k to map to index range [0, k-1]\n    i = (num * k).to_i\n    # Add num to bucket i\n    buckets[i] << num\n  end\n\n  # 2. Sort each bucket\n  buckets.each do |bucket|\n    # Use built-in sorting function, can also replace with other sorting algorithms\n    bucket.sort!\n  end\n\n  # 3. Traverse buckets to merge results\n  i = 0\n  buckets.each do |bucket|\n    bucket.each do |num|\n      nums[i] = num\n      i += 1\n    end\n  end\nend\n
","path":["Chapter 11. Sorting","11.8   Bucket Sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1182-algorithm-characteristics","level":2,"title":"11.8.2   Algorithm Characteristics","text":"

Bucket sort is suitable for processing very large data volumes. For example, if the input data contains 1 million elements and system memory cannot load all the data at once, the data can be divided into 1000 buckets, each bucket sorted separately, and then the results merged.

  • Time complexity of \\(O(n + k)\\): Assuming the elements are evenly distributed among the buckets, then the number of elements in each bucket is \\(\\frac{n}{k}\\). Assuming sorting a single bucket uses \\(O(\\frac{n}{k} \\log\\frac{n}{k})\\) time, then sorting all buckets uses \\(O(n \\log\\frac{n}{k})\\) time. When the number of buckets \\(k\\) is relatively large, the time complexity approaches \\(O(n)\\). Merging results requires traversing all buckets and elements, taking \\(O(n + k)\\) time. In the worst case, all data is distributed into one bucket, and sorting that bucket uses \\(O(n^2)\\) time.
  • Space complexity of \\(O(n + k)\\), non-in-place sorting: Additional space is required for \\(k\\) buckets and a total of \\(n\\) elements.
  • Whether bucket sort is stable depends on whether the algorithm for sorting elements within buckets is stable.
","path":["Chapter 11. Sorting","11.8   Bucket Sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1183-how-to-achieve-even-distribution","level":2,"title":"11.8.3   How to Achieve Even Distribution","text":"

Theoretically, bucket sort can achieve \\(O(n)\\) time complexity. The key is to evenly distribute elements to each bucket, because real data is often not evenly distributed. For example, if we want to evenly distribute all products on Taobao into 10 buckets by price range, there may be very many products below 100 yuan and very few above 1000 yuan. If the price intervals are evenly divided into 10, the difference in the number of products in each bucket will be very large.

To achieve even distribution, we can first set an approximate dividing line to roughly divide the data into 3 buckets. After distribution is complete, continue dividing buckets with more products into 3 buckets until the number of elements in all buckets is roughly equal.

As shown in Figure 11-14, this method essentially creates a recursion tree, with the goal of making the values of leaf nodes as even as possible. Of course, it is not necessary to divide the data into 3 buckets every round; the specific division method can be flexibly chosen according to data characteristics.

Figure 11-14   Recursively dividing buckets

If we know the probability distribution of product prices in advance, we can set the price dividing line for each bucket based on the data probability distribution. It is worth noting that the data distribution does not necessarily need to be specifically calculated, but can also be approximated using a certain probability model based on data characteristics.

As shown in Figure 11-15, we assume that product prices follow a normal distribution, which allows us to reasonably set price intervals to evenly distribute products to each bucket.

Figure 11-15   Dividing buckets based on probability distribution

","path":["Chapter 11. Sorting","11.8   Bucket Sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/","level":1,"title":"11.9   Counting Sort","text":"

Counting sort (counting sort) achieves sorting by counting the number of elements, typically applied to integer arrays.

","path":["Chapter 11. Sorting","11.9   Counting Sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1191-simple-implementation","level":2,"title":"11.9.1   Simple Implementation","text":"

Let's start with a simple example. Given an array nums of length \\(n\\), where the elements are all \"non-negative integers\", the overall flow of counting sort is shown in Figure 11-16.

  1. Traverse the array to find the largest number, denoted as \\(m\\), and then create an auxiliary array counter of length \\(m + 1\\).
  2. Use counter to count the number of occurrences of each number in nums, where counter[num] corresponds to the number of occurrences of the number num. The counting method is simple: just traverse nums (let the current number be num), and increase counter[num] by \\(1\\) in each round.
  3. Since each index of counter is naturally ordered, this is equivalent to all numbers being sorted. Next, we traverse counter and fill in nums in ascending order based on the number of occurrences of each number.

Figure 11-16   Counting sort flow

The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby counting_sort.py
def counting_sort_naive(nums: list[int]):\n    \"\"\"Counting sort\"\"\"\n    # Simple implementation, cannot be used for sorting objects\n    # 1. Count the maximum element m in the array\n    m = 0\n    for num in nums:\n        m = max(m, num)\n    # 2. Count the occurrence of each number\n    # counter[num] represents the occurrence of num\n    counter = [0] * (m + 1)\n    for num in nums:\n        counter[num] += 1\n    # 3. Traverse counter, filling each element back into the original array nums\n    i = 0\n    for num in range(m + 1):\n        for _ in range(counter[num]):\n            nums[i] = num\n            i += 1\n
counting_sort.cpp
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nvoid countingSortNaive(vector<int> &nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int num : nums) {\n        m = max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    vector<int> counter(m + 1, 0);\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
counting_sort.java
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nvoid countingSortNaive(int[] nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int num : nums) {\n        m = Math.max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int[] counter = new int[m + 1];\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
counting_sort.cs
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nvoid CountingSortNaive(int[] nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    foreach (int num in nums) {\n        m = Math.Max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int[] counter = new int[m + 1];\n    foreach (int num in nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
counting_sort.go
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfunc countingSortNaive(nums []int) {\n    // 1. Count the maximum element m in the array\n    m := 0\n    for _, num := range nums {\n        if num > m {\n            m = num\n        }\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    counter := make([]int, m+1)\n    for _, num := range nums {\n        counter[num]++\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    for i, num := 0, 0; num < m+1; num++ {\n        for j := 0; j < counter[num]; j++ {\n            nums[i] = num\n            i++\n        }\n    }\n}\n
counting_sort.swift
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfunc countingSortNaive(nums: inout [Int]) {\n    // 1. Count the maximum element m in the array\n    let m = nums.max()!\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    var counter = Array(repeating: 0, count: m + 1)\n    for num in nums {\n        counter[num] += 1\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    var i = 0\n    for num in 0 ..< m + 1 {\n        for _ in 0 ..< counter[num] {\n            nums[i] = num\n            i += 1\n        }\n    }\n}\n
counting_sort.js
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfunction countingSortNaive(nums) {\n    // 1. Count the maximum element m in the array\n    let m = Math.max(...nums);\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    const counter = new Array(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    let i = 0;\n    for (let num = 0; num < m + 1; num++) {\n        for (let j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
counting_sort.ts
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfunction countingSortNaive(nums: number[]): void {\n    // 1. Count the maximum element m in the array\n    let m: number = Math.max(...nums);\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    const counter: number[] = new Array<number>(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    let i = 0;\n    for (let num = 0; num < m + 1; num++) {\n        for (let j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
counting_sort.dart
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nvoid countingSortNaive(List<int> nums) {\n  // 1. Count the maximum element m in the array\n  int m = 0;\n  for (int _num in nums) {\n    m = max(m, _num);\n  }\n  // 2. Count the occurrence of each number\n  // counter[_num] represents occurrence count of _num\n  List<int> counter = List.filled(m + 1, 0);\n  for (int _num in nums) {\n    counter[_num]++;\n  }\n  // 3. Traverse counter, filling each element back into the original array nums\n  int i = 0;\n  for (int _num = 0; _num < m + 1; _num++) {\n    for (int j = 0; j < counter[_num]; j++, i++) {\n      nums[i] = _num;\n    }\n  }\n}\n
counting_sort.rs
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfn counting_sort_naive(nums: &mut [i32]) {\n    // 1. Count the maximum element m in the array\n    let m = *nums.iter().max().unwrap();\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    let mut counter = vec![0; m as usize + 1];\n    for &num in nums.iter() {\n        counter[num as usize] += 1;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    let mut i = 0;\n    for num in 0..m + 1 {\n        for _ in 0..counter[num as usize] {\n            nums[i] = num;\n            i += 1;\n        }\n    }\n}\n
counting_sort.c
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nvoid countingSortNaive(int nums[], int size) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > m) {\n            m = nums[i];\n        }\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int *counter = calloc(m + 1, sizeof(int));\n    for (int i = 0; i < size; i++) {\n        counter[nums[i]]++;\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n    // 4. Free memory\n    free(counter);\n}\n
counting_sort.kt
/* Counting sort */\n// Simple implementation, cannot be used for sorting objects\nfun countingSortNaive(nums: IntArray) {\n    // 1. Count the maximum element m in the array\n    var m = 0\n    for (num in nums) {\n        m = max(m, num)\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    val counter = IntArray(m + 1)\n    for (num in nums) {\n        counter[num]++\n    }\n    // 3. Traverse counter, filling each element back into the original array nums\n    var i = 0\n    for (num in 0..<m + 1) {\n        var j = 0\n        while (j < counter[num]) {\n            nums[i] = num\n            j++\n            i++\n        }\n    }\n}\n
counting_sort.rb
### Counting sort ###\ndef counting_sort_naive(nums)\n  # Simple implementation, cannot be used for sorting objects\n  # 1. Count the maximum element m in the array\n  m = 0\n  nums.each { |num| m = [m, num].max }\n  # 2. Count the occurrence of each number\n  # counter[num] represents the occurrence of num\n  counter = Array.new(m + 1, 0)\n  nums.each { |num| counter[num] += 1 }\n  # 3. Traverse counter, filling each element back into the original array nums\n  i = 0\n  for num in 0...(m + 1)\n    (0...counter[num]).each do\n      nums[i] = num\n      i += 1\n    end\n  end\nend\n

Connection between counting sort and bucket sort

From the perspective of bucket sort, we can regard each index of the counting array counter in counting sort as a bucket, and the process of counting quantities as distributing each element to the corresponding bucket. Essentially, counting sort is a special case of bucket sort for integer data.

","path":["Chapter 11. Sorting","11.9   Counting Sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1192-complete-implementation","level":2,"title":"11.9.2   Complete Implementation","text":"

Observant readers may have noticed that if the input data is objects, step 3. above becomes invalid. Suppose the input data is product objects, and we want to sort the products by price (a member variable of the class), but the above algorithm can only give the sorting result of prices.

So how can we obtain the sorting result of the original data? We first calculate the \"prefix sum\" of counter. As the name suggests, the prefix sum at index i, prefix[i], equals the sum of the first i elements of the array:

\\[ \\text{prefix}[i] = \\sum_{j=0}^i \\text{counter[j]} \\]

The prefix sum has a clear meaning: prefix[num] - 1 represents the index of the last occurrence of element num in the result array res. This information is very critical because it tells us where each element should appear in the result array. Next, we traverse each element num of the original array nums in reverse order, performing the following two steps in each iteration.

  1. Fill num into the array res at index prefix[num] - 1.
  2. Decrease the prefix sum prefix[num] by \\(1\\) to get the index for the next placement of num.

After the traversal is complete, the array res contains the sorted result, and finally res is used to overwrite the original array nums. The complete counting sort flow is shown in Figure 11-17.

<1><2><3><4><5><6><7><8>

Figure 11-17   Counting sort steps

The implementation code of counting sort is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby counting_sort.py
def counting_sort(nums: list[int]):\n    \"\"\"Counting sort\"\"\"\n    # Complete implementation, can sort objects and is a stable sort\n    # 1. Count the maximum element m in the array\n    m = max(nums)\n    # 2. Count the occurrence of each number\n    # counter[num] represents the occurrence of num\n    counter = [0] * (m + 1)\n    for num in nums:\n        counter[num] += 1\n    # 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    # counter[num]-1 is the last index where num appears in res\n    for i in range(m):\n        counter[i + 1] += counter[i]\n    # 4. Traverse nums in reverse order, placing each element into the result array res\n    # Initialize the array res to record results\n    n = len(nums)\n    res = [0] * n\n    for i in range(n - 1, -1, -1):\n        num = nums[i]\n        res[counter[num] - 1] = num  # Place num at the corresponding index\n        counter[num] -= 1  # Decrement the prefix sum by 1, getting the next index to place num\n    # Use result array res to overwrite the original array nums\n    for i in range(n):\n        nums[i] = res[i]\n
counting_sort.cpp
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nvoid countingSort(vector<int> &nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int num : nums) {\n        m = max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    vector<int> counter(m + 1, 0);\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    int n = nums.size();\n    vector<int> res(n);\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--;              // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    nums = res;\n}\n
counting_sort.java
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nvoid countingSort(int[] nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int num : nums) {\n        m = Math.max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int[] counter = new int[m + 1];\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    int n = nums.length;\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
counting_sort.cs
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nvoid CountingSort(int[] nums) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    foreach (int num in nums) {\n        m = Math.Max(m, num);\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int[] counter = new int[m + 1];\n    foreach (int num in nums) {\n        counter[num]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    int n = nums.Length;\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
counting_sort.go
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfunc countingSort(nums []int) {\n    // 1. Count the maximum element m in the array\n    m := 0\n    for _, num := range nums {\n        if num > m {\n            m = num\n        }\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    counter := make([]int, m+1)\n    for _, num := range nums {\n        counter[num]++\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for i := 0; i < m; i++ {\n        counter[i+1] += counter[i]\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    n := len(nums)\n    res := make([]int, n)\n    for i := n - 1; i >= 0; i-- {\n        num := nums[i]\n        // Place num at the corresponding index\n        res[counter[num]-1] = num\n        // Decrement the prefix sum by 1, getting the next index to place num\n        counter[num]--\n    }\n    // Use result array res to overwrite the original array nums\n    copy(nums, res)\n}\n
counting_sort.swift
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfunc countingSort(nums: inout [Int]) {\n    // 1. Count the maximum element m in the array\n    let m = nums.max()!\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    var counter = Array(repeating: 0, count: m + 1)\n    for num in nums {\n        counter[num] += 1\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for i in 0 ..< m {\n        counter[i + 1] += counter[i]\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    var res = Array(repeating: 0, count: nums.count)\n    for i in nums.indices.reversed() {\n        let num = nums[i]\n        res[counter[num] - 1] = num // Place num at the corresponding index\n        counter[num] -= 1 // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for i in nums.indices {\n        nums[i] = res[i]\n    }\n}\n
counting_sort.js
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfunction countingSort(nums) {\n    // 1. Count the maximum element m in the array\n    let m = Math.max(...nums);\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    const counter = new Array(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (let i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    const n = nums.length;\n    const res = new Array(n);\n    for (let i = n - 1; i >= 0; i--) {\n        const num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
counting_sort.ts
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfunction countingSort(nums: number[]): void {\n    // 1. Count the maximum element m in the array\n    let m: number = Math.max(...nums);\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    const counter: number[] = new Array<number>(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (let i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    const n = nums.length;\n    const res: number[] = new Array<number>(n);\n    for (let i = n - 1; i >= 0; i--) {\n        const num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
counting_sort.dart
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nvoid countingSort(List<int> nums) {\n  // 1. Count the maximum element m in the array\n  int m = 0;\n  for (int _num in nums) {\n    m = max(m, _num);\n  }\n  // 2. Count the occurrence of each number\n  // counter[_num] represents occurrence count of _num\n  List<int> counter = List.filled(m + 1, 0);\n  for (int _num in nums) {\n    counter[_num]++;\n  }\n  // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n  // That is, counter[_num]-1 is the last occurrence index of _num in res\n  for (int i = 0; i < m; i++) {\n    counter[i + 1] += counter[i];\n  }\n  // 4. Traverse nums in reverse order, placing each element into the result array res\n  // Initialize the array res to record results\n  int n = nums.length;\n  List<int> res = List.filled(n, 0);\n  for (int i = n - 1; i >= 0; i--) {\n    int _num = nums[i];\n    res[counter[_num] - 1] = _num; // Place _num at corresponding index\n    counter[_num]--; // Decrement prefix sum by 1 to get next placement index for _num\n  }\n  // Use result array res to overwrite the original array nums\n  nums.setAll(0, res);\n}\n
counting_sort.rs
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfn counting_sort(nums: &mut [i32]) {\n    // 1. Count the maximum element m in the array\n    let m = *nums.iter().max().unwrap() as usize;\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    let mut counter = vec![0; m + 1];\n    for &num in nums.iter() {\n        counter[num as usize] += 1;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for i in 0..m {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    let n = nums.len();\n    let mut res = vec![0; n];\n    for i in (0..n).rev() {\n        let num = nums[i];\n        res[counter[num as usize] - 1] = num; // Place num at the corresponding index\n        counter[num as usize] -= 1; // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    nums.copy_from_slice(&res)\n}\n
counting_sort.c
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nvoid countingSort(int nums[], int size) {\n    // 1. Count the maximum element m in the array\n    int m = 0;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > m) {\n            m = nums[i];\n        }\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    int *counter = calloc(m, sizeof(int));\n    for (int i = 0; i < size; i++) {\n        counter[nums[i]]++;\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    int *res = malloc(sizeof(int) * size);\n    for (int i = size - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--;              // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    memcpy(nums, res, size * sizeof(int));\n    // 5. Free memory\n    free(res);\n    free(counter);\n}\n
counting_sort.kt
/* Counting sort */\n// Complete implementation, can sort objects and is a stable sort\nfun countingSort(nums: IntArray) {\n    // 1. Count the maximum element m in the array\n    var m = 0\n    for (num in nums) {\n        m = max(m, num)\n    }\n    // 2. Count the occurrence of each number\n    // counter[num] represents the occurrence of num\n    val counter = IntArray(m + 1)\n    for (num in nums) {\n        counter[num]++\n    }\n    // 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n    // counter[num]-1 is the last index where num appears in res\n    for (i in 0..<m) {\n        counter[i + 1] += counter[i]\n    }\n    // 4. Traverse nums in reverse order, placing each element into the result array res\n    // Initialize the array res to record results\n    val n = nums.size\n    val res = IntArray(n)\n    for (i in n - 1 downTo 0) {\n        val num = nums[i]\n        res[counter[num] - 1] = num // Place num at the corresponding index\n        counter[num]-- // Decrement the prefix sum by 1, getting the next index to place num\n    }\n    // Use result array res to overwrite the original array nums\n    for (i in 0..<n) {\n        nums[i] = res[i]\n    }\n}\n
counting_sort.rb
### Counting sort ###\ndef counting_sort(nums)\n  # Complete implementation, can sort objects and is a stable sort\n  # 1. Count the maximum element m in the array\n  m = nums.max\n  # 2. Count the occurrence of each number\n  # counter[num] represents the occurrence of num\n  counter = Array.new(m + 1, 0)\n  nums.each { |num| counter[num] += 1 }\n  # 3. Calculate the prefix sum of counter, converting \"occurrence count\" to \"tail index\"\n  # counter[num]-1 is the last index where num appears in res\n  (0...m).each { |i| counter[i + 1] += counter[i] }\n  # 4. Traverse nums in reverse, fill elements into result array res\n  # Initialize the array res to record results\n  n = nums.length\n  res = Array.new(n, 0)\n  (n - 1).downto(0).each do |i|\n    num = nums[i]\n    res[counter[num] - 1] = num # Place num at the corresponding index\n    counter[num] -= 1 # Decrement the prefix sum by 1, getting the next index to place num\n  end\n  # Use result array res to overwrite the original array nums\n  (0...n).each { |i| nums[i] = res[i] }\nend\n
","path":["Chapter 11. Sorting","11.9   Counting Sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1193-algorithm-characteristics","level":2,"title":"11.9.3   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n + m)\\), non-adaptive sorting: Involves traversing nums and traversing counter, both using linear time. Generally, \\(n \\gg m\\), and time complexity tends toward \\(O(n)\\).
  • Space complexity of \\(O(n + m)\\), non-in-place sorting: Uses arrays res and counter of lengths \\(n\\) and \\(m\\) respectively.
  • Stable sorting: Since elements are filled into res in a \"right-to-left\" order, traversing nums in reverse can avoid changing the relative positions of equal elements, thereby achieving stable sorting. In fact, traversing nums in forward order can also yield correct sorting results, but the result would be unstable.
","path":["Chapter 11. Sorting","11.9   Counting Sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1194-limitations","level":2,"title":"11.9.4   Limitations","text":"

By this point, you might think counting sort is very clever, as it can achieve efficient sorting just by counting quantities. However, the prerequisites for using counting sort are relatively strict.

Counting sort is only suitable for non-negative integers. If you want to apply it to other types of data, you need to ensure that the data can be converted to non-negative integers without changing the relative size relationships between elements. For example, for an integer array containing negative numbers, you can first add a constant to all numbers to convert them all to positive numbers, and then convert them back after sorting is complete.

Counting sort is suitable for situations where the data volume is large but the data range is small. For example, in the above example, \\(m\\) cannot be too large, otherwise it will occupy too much space. And when \\(n \\ll m\\), counting sort uses \\(O(m)\\) time, which may be slower than \\(O(n \\log n)\\) sorting algorithms.

","path":["Chapter 11. Sorting","11.9   Counting Sort"],"tags":[]},{"location":"chapter_sorting/heap_sort/","level":1,"title":"11.7   Heap Sort","text":"

Tip

Before reading this section, please ensure you have completed the \"Heap\" chapter.

Heap sort (heap sort) is an efficient sorting algorithm based on the heap data structure. We can use the \"build heap operation\" and \"element out-heap operation\" that we have already learned to implement heap sort.

  1. Input the array and build a min-heap, at which point the smallest element is at the heap top.
  2. Continuously perform the out-heap operation, record the out-heap elements in sequence, and an ascending sorted sequence can be obtained.

Although the above method is feasible, it requires an additional array to save the popped elements, which is quite wasteful of space. In practice, we usually use a more elegant implementation method.

","path":["Chapter 11. Sorting","11.7   Heap Sort"],"tags":[]},{"location":"chapter_sorting/heap_sort/#1171-algorithm-flow","level":2,"title":"11.7.1   Algorithm Flow","text":"

Assume the array length is \\(n\\). The flow of heap sort is shown in Figure 11-12.

  1. Input the array and build a max-heap. After completion, the largest element is at the heap top.
  2. Swap the heap top element (first element) with the heap bottom element (last element). After the swap is complete, reduce the heap length by \\(1\\) and increase the count of sorted elements by \\(1\\).
  3. Starting from the heap top element, perform top-to-bottom heapify operation (sift down). After heapify is complete, the heap property is restored.
  4. Loop through steps 2. and 3. After looping \\(n - 1\\) rounds, the array sorting can be completed.

Tip

In fact, the element out-heap operation also includes steps 2. and 3., with just an additional step to pop the element.

<1><2><3><4><5><6><7><8><9><10><11><12>

Figure 11-12   Heap sort steps

In the code implementation, we use the same top-to-bottom heapify function sift_down() from the \"Heap\" chapter. It is worth noting that since the heap length will decrease as the largest element is extracted, we need to add a length parameter \\(n\\) to the sift_down() function to specify the current effective length of the heap. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby heap_sort.py
def sift_down(nums: list[int], n: int, i: int):\n    \"\"\"Heap length is n, start heapifying node i, from top to bottom\"\"\"\n    while True:\n        # Determine the largest node among i, l, r, noted as ma\n        l = 2 * i + 1\n        r = 2 * i + 2\n        ma = i\n        if l < n and nums[l] > nums[ma]:\n            ma = l\n        if r < n and nums[r] > nums[ma]:\n            ma = r\n        # If node i is the largest or indices l, r are out of bounds, no further heapification needed, break\n        if ma == i:\n            break\n        # Swap two nodes\n        nums[i], nums[ma] = nums[ma], nums[i]\n        # Loop downwards heapification\n        i = ma\n\ndef heap_sort(nums: list[int]):\n    \"\"\"Heap sort\"\"\"\n    # Build heap operation: heapify all nodes except leaves\n    for i in range(len(nums) // 2 - 1, -1, -1):\n        sift_down(nums, len(nums), i)\n    # Extract the largest element from the heap and repeat for n-1 rounds\n    for i in range(len(nums) - 1, 0, -1):\n        # Swap the root node with the rightmost leaf node (swap the first element with the last element)\n        nums[0], nums[i] = nums[i], nums[0]\n        # Start heapifying the root node, from top to bottom\n        sift_down(nums, i, 0)\n
heap_sort.cpp
/* Heap length is n, start heapifying node i, from top to bottom */\nvoid siftDown(vector<int> &nums, int n, int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Swap two nodes\n        if (ma == i) {\n            break;\n        }\n        // Swap two nodes\n        swap(nums[i], nums[ma]);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(vector<int> &nums) {\n    // Build heap operation: heapify all nodes except leaves\n    for (int i = nums.size() / 2 - 1; i >= 0; --i) {\n        siftDown(nums, nums.size(), i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (int i = nums.size() - 1; i > 0; --i) {\n        // Delete node\n        swap(nums[0], nums[i]);\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0);\n    }\n}\n
heap_sort.java
/* Heap length is n, start heapifying node i, from top to bottom */\nvoid siftDown(int[] nums, int n, int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Swap two nodes\n        if (ma == i)\n            break;\n        // Swap two nodes\n        int temp = nums[i];\n        nums[i] = nums[ma];\n        nums[ma] = temp;\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(int[] nums) {\n    // Build heap operation: heapify all nodes except leaves\n    for (int i = nums.length / 2 - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Delete node\n        int tmp = nums[0];\n        nums[0] = nums[i];\n        nums[i] = tmp;\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0);\n    }\n}\n
heap_sort.cs
/* Heap length is n, start heapifying node i, from top to bottom */\nvoid SiftDown(int[] nums, int n, int i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Swap two nodes\n        if (ma == i)\n            break;\n        // Swap two nodes\n        (nums[ma], nums[i]) = (nums[i], nums[ma]);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid HeapSort(int[] nums) {\n    // Build heap operation: heapify all nodes except leaves\n    for (int i = nums.Length / 2 - 1; i >= 0; i--) {\n        SiftDown(nums, nums.Length, i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Delete node\n        (nums[i], nums[0]) = (nums[0], nums[i]);\n        // Start heapifying the root node, from top to bottom\n        SiftDown(nums, i, 0);\n    }\n}\n
heap_sort.go
/* Heap length is n, start heapifying node i, from top to bottom */\nfunc siftDown(nums *[]int, n, i int) {\n    for true {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        l := 2*i + 1\n        r := 2*i + 2\n        ma := i\n        if l < n && (*nums)[l] > (*nums)[ma] {\n            ma = l\n        }\n        if r < n && (*nums)[r] > (*nums)[ma] {\n            ma = r\n        }\n        // Swap two nodes\n        if ma == i {\n            break\n        }\n        // Swap two nodes\n        (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i]\n        // Loop downwards heapification\n        i = ma\n    }\n}\n\n/* Heap sort */\nfunc heapSort(nums *[]int) {\n    // Build heap operation: heapify all nodes except leaves\n    for i := len(*nums)/2 - 1; i >= 0; i-- {\n        siftDown(nums, len(*nums), i)\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for i := len(*nums) - 1; i > 0; i-- {\n        // Delete node\n        (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0]\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0)\n    }\n}\n
heap_sort.swift
/* Heap length is n, start heapifying node i, from top to bottom */\nfunc siftDown(nums: inout [Int], n: Int, i: Int) {\n    var i = i\n    while true {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let l = 2 * i + 1\n        let r = 2 * i + 2\n        var ma = i\n        if l < n, nums[l] > nums[ma] {\n            ma = l\n        }\n        if r < n, nums[r] > nums[ma] {\n            ma = r\n        }\n        // Swap two nodes\n        if ma == i {\n            break\n        }\n        // Swap two nodes\n        nums.swapAt(i, ma)\n        // Loop downwards heapification\n        i = ma\n    }\n}\n\n/* Heap sort */\nfunc heapSort(nums: inout [Int]) {\n    // Build heap operation: heapify all nodes except leaves\n    for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) {\n        siftDown(nums: &nums, n: nums.count, i: i)\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for i in nums.indices.dropFirst().reversed() {\n        // Delete node\n        nums.swapAt(0, i)\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums: &nums, n: i, i: 0)\n    }\n}\n
heap_sort.js
/* Heap length is n, start heapifying node i, from top to bottom */\nfunction siftDown(nums, n, i) {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let ma = i;\n        if (l < n && nums[l] > nums[ma]) {\n            ma = l;\n        }\n        if (r < n && nums[r] > nums[ma]) {\n            ma = r;\n        }\n        // Swap two nodes\n        if (ma === i) {\n            break;\n        }\n        // Swap two nodes\n        [nums[i], nums[ma]] = [nums[ma], nums[i]];\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfunction heapSort(nums) {\n    // Build heap operation: heapify all nodes except leaves\n    for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Delete node\n        [nums[0], nums[i]] = [nums[i], nums[0]];\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0);\n    }\n}\n
heap_sort.ts
/* Heap length is n, start heapifying node i, from top to bottom */\nfunction siftDown(nums: number[], n: number, i: number): void {\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let ma = i;\n        if (l < n && nums[l] > nums[ma]) {\n            ma = l;\n        }\n        if (r < n && nums[r] > nums[ma]) {\n            ma = r;\n        }\n        // Swap two nodes\n        if (ma === i) {\n            break;\n        }\n        // Swap two nodes\n        [nums[i], nums[ma]] = [nums[ma], nums[i]];\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfunction heapSort(nums: number[]): void {\n    // Build heap operation: heapify all nodes except leaves\n    for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Delete node\n        [nums[0], nums[i]] = [nums[i], nums[0]];\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0);\n    }\n}\n
heap_sort.dart
/* Heap length is n, start heapifying node i, from top to bottom */\nvoid siftDown(List<int> nums, int n, int i) {\n  while (true) {\n    // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n    int l = 2 * i + 1;\n    int r = 2 * i + 2;\n    int ma = i;\n    if (l < n && nums[l] > nums[ma]) ma = l;\n    if (r < n && nums[r] > nums[ma]) ma = r;\n    // Swap two nodes\n    if (ma == i) break;\n    // Swap two nodes\n    int temp = nums[i];\n    nums[i] = nums[ma];\n    nums[ma] = temp;\n    // Loop downwards heapification\n    i = ma;\n  }\n}\n\n/* Heap sort */\nvoid heapSort(List<int> nums) {\n  // Build heap operation: heapify all nodes except leaves\n  for (int i = nums.length ~/ 2 - 1; i >= 0; i--) {\n    siftDown(nums, nums.length, i);\n  }\n  // Extract the largest element from the heap and repeat for n-1 rounds\n  for (int i = nums.length - 1; i > 0; i--) {\n    // Delete node\n    int tmp = nums[0];\n    nums[0] = nums[i];\n    nums[i] = tmp;\n    // Start heapifying the root node, from top to bottom\n    siftDown(nums, i, 0);\n  }\n}\n
heap_sort.rs
/* Heap length is n, start heapifying node i, from top to bottom */\nfn sift_down(nums: &mut [i32], n: usize, mut i: usize) {\n    loop {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let mut ma = i;\n        if l < n && nums[l] > nums[ma] {\n            ma = l;\n        }\n        if r < n && nums[r] > nums[ma] {\n            ma = r;\n        }\n        // Swap two nodes\n        if ma == i {\n            break;\n        }\n        // Swap two nodes\n        nums.swap(i, ma);\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfn heap_sort(nums: &mut [i32]) {\n    // Build heap operation: heapify all nodes except leaves\n    for i in (0..nums.len() / 2).rev() {\n        sift_down(nums, nums.len(), i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for i in (1..nums.len()).rev() {\n        // Delete node\n        nums.swap(0, i);\n        // Start heapifying the root node, from top to bottom\n        sift_down(nums, i, 0);\n    }\n}\n
heap_sort.c
/* Heap length is n, start heapifying node i, from top to bottom */\nvoid siftDown(int nums[], int n, int i) {\n    while (1) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Swap two nodes\n        if (ma == i) {\n            break;\n        }\n        // Swap two nodes\n        int temp = nums[i];\n        nums[i] = nums[ma];\n        nums[ma] = temp;\n        // Loop downwards heapification\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(int nums[], int n) {\n    // Build heap operation: heapify all nodes except leaves\n    for (int i = n / 2 - 1; i >= 0; --i) {\n        siftDown(nums, n, i);\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (int i = n - 1; i > 0; --i) {\n        // Delete node\n        int tmp = nums[0];\n        nums[0] = nums[i];\n        nums[i] = tmp;\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0);\n    }\n}\n
heap_sort.kt
/* Heap length is n, start heapifying node i, from top to bottom */\nfun siftDown(nums: IntArray, n: Int, li: Int) {\n    var i = li\n    while (true) {\n        // If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n        val l = 2 * i + 1\n        val r = 2 * i + 2\n        var ma = i\n        if (l < n && nums[l] > nums[ma]) \n            ma = l\n        if (r < n && nums[r] > nums[ma]) \n            ma = r\n        // Swap two nodes\n        if (ma == i) \n            break\n        // Swap two nodes\n        val temp = nums[i]\n        nums[i] = nums[ma]\n        nums[ma] = temp\n        // Loop downwards heapification\n        i = ma\n    }\n}\n\n/* Heap sort */\nfun heapSort(nums: IntArray) {\n    // Build heap operation: heapify all nodes except leaves\n    for (i in nums.size / 2 - 1 downTo 0) {\n        siftDown(nums, nums.size, i)\n    }\n    // Extract the largest element from the heap and repeat for n-1 rounds\n    for (i in nums.size - 1 downTo 1) {\n        // Delete node\n        val temp = nums[0]\n        nums[0] = nums[i]\n        nums[i] = temp\n        // Start heapifying the root node, from top to bottom\n        siftDown(nums, i, 0)\n    }\n}\n
heap_sort.rb
### Heap length is n, heapify from node i, top to bottom ###\ndef sift_down(nums, n, i)\n  while true\n    # If node i is largest or indices l, r are out of bounds, no need to continue heapify, break\n    l = 2 * i + 1\n    r = 2 * i + 2\n    ma = i\n    ma = l if l < n && nums[l] > nums[ma]\n    ma = r if r < n && nums[r] > nums[ma]\n    # Swap two nodes\n    break if ma == i\n    # Swap two nodes\n    nums[i], nums[ma] = nums[ma], nums[i]\n    # Loop downwards heapification\n    i = ma\n  end\nend\n\n### Heap sort ###\ndef heap_sort(nums)\n  # Build heap operation: heapify all nodes except leaves\n  (nums.length / 2 - 1).downto(0) do |i|\n    sift_down(nums, nums.length, i)\n  end\n  # Extract the largest element from the heap and repeat for n-1 rounds\n  (nums.length - 1).downto(1) do |i|\n    # Delete node\n    nums[0], nums[i] = nums[i], nums[0]\n    # Start heapifying the root node, from top to bottom\n    sift_down(nums, i, 0)\n  end\nend\n
","path":["Chapter 11. Sorting","11.7   Heap Sort"],"tags":[]},{"location":"chapter_sorting/heap_sort/#1172-algorithm-characteristics","level":2,"title":"11.7.2   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n \\log n)\\), non-adaptive sorting: The build heap operation uses \\(O(n)\\) time. Extracting the largest element from the heap has a time complexity of \\(O(\\log n)\\), looping a total of \\(n - 1\\) rounds.
  • Space complexity of \\(O(1)\\), in-place sorting: A few pointer variables use \\(O(1)\\) space. Element swapping and heapify operations are both performed on the original array.
  • Non-stable sorting: When swapping the heap top element and heap bottom element, the relative positions of equal elements may change.
","path":["Chapter 11. Sorting","11.7   Heap Sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/","level":1,"title":"11.4   Insertion Sort","text":"

Insertion sort (insertion sort) is a simple sorting algorithm that works very similarly to the process of manually organizing a deck of cards.

Specifically, we select a base element from the unsorted interval, compare the element with elements in the sorted interval to its left one by one, and insert the element into the correct position.

Figure 11-6 shows the operation flow of inserting an element into the array. Let the base element be base. We need to move all elements from the target index to base one position to the right, and then assign base to the target index.

Figure 11-6   Single insertion operation

","path":["Chapter 11. Sorting","11.4   Insertion Sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1141-algorithm-flow","level":2,"title":"11.4.1   Algorithm Flow","text":"

The overall flow of insertion sort is shown in Figure 11-7.

  1. Initially, the first element of the array has completed sorting.
  2. Select the second element of the array as base, and after inserting it into the correct position, the first 2 elements of the array are sorted.
  3. Select the third element as base, and after inserting it into the correct position, the first 3 elements of the array are sorted.
  4. And so on. In the last round, select the last element as base, and after inserting it into the correct position, all elements are sorted.

Figure 11-7   Insertion sort flow

Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby insertion_sort.py
def insertion_sort(nums: list[int]):\n    \"\"\"Insertion sort\"\"\"\n    # Outer loop: sorted interval is [0, i-1]\n    for i in range(1, len(nums)):\n        base = nums[i]\n        j = i - 1\n        # Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while j >= 0 and nums[j] > base:\n            nums[j + 1] = nums[j]  # Move nums[j] to the right by one position\n            j -= 1\n        nums[j + 1] = base  # Assign base to the correct position\n
insertion_sort.cpp
/* Insertion sort */\nvoid insertionSort(vector<int> &nums) {\n    // Outer loop: sorted interval is [0, i-1]\n    for (int i = 1; i < nums.size(); i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n            j--;\n        }\n        nums[j + 1] = base; // Assign base to the correct position\n    }\n}\n
insertion_sort.java
/* Insertion sort */\nvoid insertionSort(int[] nums) {\n    // Outer loop: sorted interval is [0, i-1]\n    for (int i = 1; i < nums.length; i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n            j--;\n        }\n        nums[j + 1] = base;        // Assign base to the correct position\n    }\n}\n
insertion_sort.cs
/* Insertion sort */\nvoid InsertionSort(int[] nums) {\n    // Outer loop: sorted interval is [0, i-1]\n    for (int i = 1; i < nums.Length; i++) {\n        int bas = nums[i], j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > bas) {\n            nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n            j--;\n        }\n        nums[j + 1] = bas;         // Assign base to the correct position\n    }\n}\n
insertion_sort.go
/* Insertion sort */\nfunc insertionSort(nums []int) {\n    // Outer loop: sorted interval is [0, i-1]\n    for i := 1; i < len(nums); i++ {\n        base := nums[i]\n        j := i - 1\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        for j >= 0 && nums[j] > base {\n            nums[j+1] = nums[j] // Move nums[j] to the right by one position\n            j--\n        }\n        nums[j+1] = base // Assign base to the correct position\n    }\n}\n
insertion_sort.swift
/* Insertion sort */\nfunc insertionSort(nums: inout [Int]) {\n    // Outer loop: sorted interval is [0, i-1]\n    for i in nums.indices.dropFirst() {\n        let base = nums[i]\n        var j = i - 1\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while j >= 0, nums[j] > base {\n            nums[j + 1] = nums[j] // Move nums[j] to the right by one position\n            j -= 1\n        }\n        nums[j + 1] = base // Assign base to the correct position\n    }\n}\n
insertion_sort.js
/* Insertion sort */\nfunction insertionSort(nums) {\n    // Outer loop: sorted interval is [0, i-1]\n    for (let i = 1; i < nums.length; i++) {\n        let base = nums[i],\n            j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n            j--;\n        }\n        nums[j + 1] = base; // Assign base to the correct position\n    }\n}\n
insertion_sort.ts
/* Insertion sort */\nfunction insertionSort(nums: number[]): void {\n    // Outer loop: sorted interval is [0, i-1]\n    for (let i = 1; i < nums.length; i++) {\n        const base = nums[i];\n        let j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n            j--;\n        }\n        nums[j + 1] = base; // Assign base to the correct position\n    }\n}\n
insertion_sort.dart
/* Insertion sort */\nvoid insertionSort(List<int> nums) {\n  // Outer loop: sorted interval is [0, i-1]\n  for (int i = 1; i < nums.length; i++) {\n    int base = nums[i], j = i - 1;\n    // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n    while (j >= 0 && nums[j] > base) {\n      nums[j + 1] = nums[j]; // Move nums[j] to the right by one position\n      j--;\n    }\n    nums[j + 1] = base; // Assign base to the correct position\n  }\n}\n
insertion_sort.rs
/* Insertion sort */\nfn insertion_sort(nums: &mut [i32]) {\n    // Outer loop: sorted interval is [0, i-1]\n    for i in 1..nums.len() {\n        let (base, mut j) = (nums[i], (i - 1) as i32);\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while j >= 0 && nums[j as usize] > base {\n            nums[(j + 1) as usize] = nums[j as usize]; // Move nums[j] to the right by one position\n            j -= 1;\n        }\n        nums[(j + 1) as usize] = base; // Assign base to the correct position\n    }\n}\n
insertion_sort.c
/* Insertion sort */\nvoid insertionSort(int nums[], int size) {\n    // Outer loop: sorted interval is [0, i-1]\n    for (int i = 1; i < size; i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            // Move nums[j] to the right by one position\n            nums[j + 1] = nums[j];\n            j--;\n        }\n        // Assign base to the correct position\n        nums[j + 1] = base;\n    }\n}\n
insertion_sort.kt
/* Insertion sort */\nfun insertionSort(nums: IntArray) {\n    // Outer loop: sorted elements are 1, 2, ..., n\n    for (i in nums.indices) {\n        val base = nums[i]\n        var j = i - 1\n        // Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j] // Move nums[j] to the right by one position\n            j--\n        }\n        nums[j + 1] = base        // Assign base to the correct position\n    }\n}\n
insertion_sort.rb
### Insertion sort ###\ndef insertion_sort(nums)\n  n = nums.length\n  # Outer loop: sorted interval is [0, i-1]\n  for i in 1...n\n    base = nums[i]\n    j = i - 1\n    # Inner loop: insert base into the correct position within the sorted interval [0, i-1]\n    while j >= 0 && nums[j] > base\n      nums[j + 1] = nums[j] # Move nums[j] to the right by one position\n      j -= 1\n    end\n    nums[j + 1] = base # Assign base to the correct position\n  end\nend\n
","path":["Chapter 11. Sorting","11.4   Insertion Sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1142-algorithm-characteristics","level":2,"title":"11.4.2   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n^2)\\), adaptive sorting: In the worst case, each insertion operation requires loops of \\(n - 1\\), \\(n-2\\), \\(\\dots\\), \\(2\\), \\(1\\), summing to \\((n - 1) n / 2\\), so the time complexity is \\(O(n^2)\\). When encountering ordered data, the insertion operation will terminate early. When the input array is completely ordered, insertion sort achieves the best-case time complexity of \\(O(n)\\).
  • Space complexity of \\(O(1)\\), in-place sorting: Pointers \\(i\\) and \\(j\\) use a constant amount of extra space.
  • Stable sorting: During the insertion operation process, we insert elements to the right of equal elements, without changing their order.
","path":["Chapter 11. Sorting","11.4   Insertion Sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1143-advantages-of-insertion-sort","level":2,"title":"11.4.3   Advantages of Insertion Sort","text":"

The time complexity of insertion sort is \\(O(n^2)\\), while the time complexity of quick sort, which we will learn about next, is \\(O(n \\log n)\\). Although insertion sort has a higher time complexity, insertion sort is usually faster for smaller data volumes.

This conclusion is similar to the applicable situations of linear search and binary search. Algorithms like quick sort with \\(O(n \\log n)\\) complexity are sorting algorithms based on divide-and-conquer strategy and often contain more unit computation operations. When the data volume is small, \\(n^2\\) and \\(n \\log n\\) are numerically close, and complexity does not dominate; the number of unit operations per round plays a decisive role.

In fact, the built-in sorting functions in many programming languages (such as Java) adopt insertion sort. The general approach is: for long arrays, use sorting algorithms based on divide-and-conquer strategy, such as quick sort; for short arrays, directly use insertion sort.

Although bubble sort, selection sort, and insertion sort all have a time complexity of \\(O(n^2)\\), in actual situations, insertion sort is used significantly more frequently than bubble sort and selection sort, mainly for the following reasons.

  • Bubble sort is based on element swapping, requiring the use of a temporary variable, involving 3 unit operations; insertion sort is based on element assignment, requiring only 1 unit operation. Therefore, the computational overhead of bubble sort is usually higher than that of insertion sort.
  • Selection sort has a time complexity of \\(O(n^2)\\) in any case. If given a set of partially ordered data, insertion sort is usually more efficient than selection sort.
  • Selection sort is unstable and cannot be applied to multi-level sorting.
","path":["Chapter 11. Sorting","11.4   Insertion Sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/","level":1,"title":"11.6   Merge Sort","text":"

Merge sort (merge sort) is a sorting algorithm based on the divide-and-conquer strategy, which includes the \"divide\" and \"merge\" phases shown in Figure 11-10.

  1. Divide phase: Recursively split the array from the midpoint, transforming the sorting problem of a long array into the sorting problems of shorter arrays.
  2. Merge phase: When the sub-array length is 1, terminate the division and start merging, continuously merging two shorter sorted arrays into one longer sorted array until the process is complete.

Figure 11-10   Divide and merge phases of merge sort

","path":["Chapter 11. Sorting","11.6   Merge Sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1161-algorithm-flow","level":2,"title":"11.6.1   Algorithm Flow","text":"

As shown in Figure 11-11, the \"divide phase\" recursively splits the array from the midpoint into two sub-arrays from top to bottom.

  1. Calculate the array midpoint mid, recursively divide the left sub-array (interval [left, mid]) and right sub-array (interval [mid + 1, right]).
  2. Recursively execute step 1. until the sub-array interval length is 1, then terminate.

The \"merge phase\" merges the left sub-array and right sub-array into a sorted array from bottom to top. Note that merging starts from sub-arrays of length 1, and each sub-array in the merge phase is sorted.

<1><2><3><4><5><6><7><8><9><10>

Figure 11-11   Merge sort steps

It can be observed that the recursive order of merge sort is consistent with the post-order traversal of a binary tree.

  • Post-order traversal: First recursively traverse the left subtree, then recursively traverse the right subtree, and finally process the root node.
  • Merge sort: First recursively process the left sub-array, then recursively process the right sub-array, and finally perform the merge.

The implementation of merge sort is shown in the code below. Note that the interval to be merged in nums is [left, right], while the corresponding interval in tmp is [0, right - left].

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby merge_sort.py
def merge(nums: list[int], left: int, mid: int, right: int):\n    \"\"\"Merge left subarray and right subarray\"\"\"\n    # Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    # Create a temporary array tmp to store the merged results\n    tmp = [0] * (right - left + 1)\n    # Initialize the start indices of the left and right subarrays\n    i, j, k = left, mid + 1, 0\n    # While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while i <= mid and j <= right:\n        if nums[i] <= nums[j]:\n            tmp[k] = nums[i]\n            i += 1\n        else:\n            tmp[k] = nums[j]\n            j += 1\n        k += 1\n    # Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid:\n        tmp[k] = nums[i]\n        i += 1\n        k += 1\n    while j <= right:\n        tmp[k] = nums[j]\n        j += 1\n        k += 1\n    # Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for k in range(0, len(tmp)):\n        nums[left + k] = tmp[k]\n\ndef merge_sort(nums: list[int], left: int, right: int):\n    \"\"\"Merge sort\"\"\"\n    # Termination condition\n    if left >= right:\n        return  # Terminate recursion when subarray length is 1\n    # Divide and conquer stage\n    mid = (left + right) // 2  # Calculate midpoint\n    merge_sort(nums, left, mid)  # Recursively process the left subarray\n    merge_sort(nums, mid + 1, right)  # Recursively process the right subarray\n    # Merge stage\n    merge(nums, left, mid, right)\n
merge_sort.cpp
/* Merge left subarray and right subarray */\nvoid merge(vector<int> &nums, int left, int mid, int right) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    vector<int> tmp(right - left + 1);\n    // Initialize the start indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmp.size(); k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid mergeSort(vector<int> &nums, int left, int right) {\n    // Termination condition\n    if (left >= right)\n        return; // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    int mid = left + (right - left) / 2;    // Calculate midpoint\n    mergeSort(nums, left, mid);      // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.java
/* Merge left subarray and right subarray */\nvoid merge(int[] nums, int left, int mid, int right) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    int[] tmp = new int[right - left + 1];\n    // Initialize the start indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid mergeSort(int[] nums, int left, int right) {\n    // Termination condition\n    if (left >= right)\n        return; // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    int mid = left + (right - left) / 2; // Calculate midpoint\n    mergeSort(nums, left, mid); // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.cs
/* Merge left subarray and right subarray */\nvoid Merge(int[] nums, int left, int mid, int right) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    int[] tmp = new int[right - left + 1];\n    // Initialize the start indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmp.Length; ++k) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid MergeSort(int[] nums, int left, int right) {\n    // Termination condition\n    if (left >= right) return;       // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    int mid = left + (right - left) / 2;    // Calculate midpoint\n    MergeSort(nums, left, mid);      // Recursively process the left subarray\n    MergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    Merge(nums, left, mid, right);\n}\n
merge_sort.go
/* Merge left subarray and right subarray */\nfunc merge(nums []int, left, mid, right int) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    tmp := make([]int, right-left+1)\n    // Initialize the start indices of the left and right subarrays\n    i, j, k := left, mid+1, 0\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    for i <= mid && j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i]\n            i++\n        } else {\n            tmp[k] = nums[j]\n            j++\n        }\n        k++\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    for i <= mid {\n        tmp[k] = nums[i]\n        i++\n        k++\n    }\n    for j <= right {\n        tmp[k] = nums[j]\n        j++\n        k++\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for k := 0; k < len(tmp); k++ {\n        nums[left+k] = tmp[k]\n    }\n}\n\n/* Merge sort */\nfunc mergeSort(nums []int, left, right int) {\n    // Termination condition\n    if left >= right {\n        return\n    }\n    // Divide and conquer stage\n    mid := left + (right - left) / 2\n    mergeSort(nums, left, mid)\n    mergeSort(nums, mid+1, right)\n    // Merge stage\n    merge(nums, left, mid, right)\n}\n
merge_sort.swift
/* Merge left subarray and right subarray */\nfunc merge(nums: inout [Int], left: Int, mid: Int, right: Int) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    var tmp = Array(repeating: 0, count: right - left + 1)\n    // Initialize the start indices of the left and right subarrays\n    var i = left, j = mid + 1, k = 0\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while i <= mid, j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i]\n            i += 1\n        } else {\n            tmp[k] = nums[j]\n            j += 1\n        }\n        k += 1\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid {\n        tmp[k] = nums[i]\n        i += 1\n        k += 1\n    }\n    while j <= right {\n        tmp[k] = nums[j]\n        j += 1\n        k += 1\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for k in tmp.indices {\n        nums[left + k] = tmp[k]\n    }\n}\n\n/* Merge sort */\nfunc mergeSort(nums: inout [Int], left: Int, right: Int) {\n    // Termination condition\n    if left >= right { // Terminate recursion when subarray length is 1\n        return\n    }\n    // Divide and conquer stage\n    let mid = left + (right - left) / 2 // Calculate midpoint\n    mergeSort(nums: &nums, left: left, right: mid) // Recursively process the left subarray\n    mergeSort(nums: &nums, left: mid + 1, right: right) // Recursively process the right subarray\n    // Merge stage\n    merge(nums: &nums, left: left, mid: mid, right: right)\n}\n
merge_sort.js
/* Merge left subarray and right subarray */\nfunction merge(nums, left, mid, right) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    const tmp = new Array(right - left + 1);\n    // Initialize the start indices of the left and right subarrays\n    let i = left,\n        j = mid + 1,\n        k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfunction mergeSort(nums, left, right) {\n    // Termination condition\n    if (left >= right) return; // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    let mid = Math.floor(left + (right - left) / 2); // Calculate midpoint\n    mergeSort(nums, left, mid); // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.ts
/* Merge left subarray and right subarray */\nfunction merge(nums: number[], left: number, mid: number, right: number): void {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    const tmp = new Array(right - left + 1);\n    // Initialize the start indices of the left and right subarrays\n    let i = left,\n        j = mid + 1,\n        k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfunction mergeSort(nums: number[], left: number, right: number): void {\n    // Termination condition\n    if (left >= right) return; // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    let mid = Math.floor(left + (right - left) / 2); // Calculate midpoint\n    mergeSort(nums, left, mid); // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.dart
/* Merge left subarray and right subarray */\nvoid merge(List<int> nums, int left, int mid, int right) {\n  // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n  // Create a temporary array tmp to store the merged results\n  List<int> tmp = List.filled(right - left + 1, 0);\n  // Initialize the start indices of the left and right subarrays\n  int i = left, j = mid + 1, k = 0;\n  // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n  while (i <= mid && j <= right) {\n    if (nums[i] <= nums[j])\n      tmp[k++] = nums[i++];\n    else\n      tmp[k++] = nums[j++];\n  }\n  // Copy the remaining elements of the left and right subarrays into the temporary array\n  while (i <= mid) {\n    tmp[k++] = nums[i++];\n  }\n  while (j <= right) {\n    tmp[k++] = nums[j++];\n  }\n  // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n  for (k = 0; k < tmp.length; k++) {\n    nums[left + k] = tmp[k];\n  }\n}\n\n/* Merge sort */\nvoid mergeSort(List<int> nums, int left, int right) {\n  // Termination condition\n  if (left >= right) return; // Terminate recursion when subarray length is 1\n  // Divide and conquer stage\n  int mid = left + (right - left) ~/ 2; // Calculate midpoint\n  mergeSort(nums, left, mid); // Recursively process the left subarray\n  mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n  // Merge stage\n  merge(nums, left, mid, right);\n}\n
merge_sort.rs
/* Merge left subarray and right subarray */\nfn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    let tmp_size = right - left + 1;\n    let mut tmp = vec![0; tmp_size];\n    // Initialize the start indices of the left and right subarrays\n    let (mut i, mut j, mut k) = (left, mid + 1, 0);\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while i <= mid && j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i];\n            i += 1;\n        } else {\n            tmp[k] = nums[j];\n            j += 1;\n        }\n        k += 1;\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid {\n        tmp[k] = nums[i];\n        k += 1;\n        i += 1;\n    }\n    while j <= right {\n        tmp[k] = nums[j];\n        k += 1;\n        j += 1;\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for k in 0..tmp_size {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfn merge_sort(nums: &mut [i32], left: usize, right: usize) {\n    // Termination condition\n    if left >= right {\n        return; // Terminate recursion when subarray length is 1\n    }\n\n    // Divide and conquer stage\n    let mid = left + (right - left) / 2; // Calculate midpoint\n    merge_sort(nums, left, mid); // Recursively process the left subarray\n    merge_sort(nums, mid + 1, right); // Recursively process the right subarray\n\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.c
/* Merge left subarray and right subarray */\nvoid merge(int *nums, int left, int mid, int right) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    int tmpSize = right - left + 1;\n    int *tmp = (int *)malloc(tmpSize * sizeof(int));\n    // Initialize the start indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (k = 0; k < tmpSize; ++k) {\n        nums[left + k] = tmp[k];\n    }\n    // Free memory\n    free(tmp);\n}\n\n/* Merge sort */\nvoid mergeSort(int *nums, int left, int right) {\n    // Termination condition\n    if (left >= right)\n        return; // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    int mid = left + (right - left) / 2;    // Calculate midpoint\n    mergeSort(nums, left, mid);      // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right); // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right);\n}\n
merge_sort.kt
/* Merge left subarray and right subarray */\nfun merge(nums: IntArray, left: Int, mid: Int, right: Int) {\n    // Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n    // Create a temporary array tmp to store the merged results\n    val tmp = IntArray(right - left + 1)\n    // Initialize the start indices of the left and right subarrays\n    var i = left\n    var j = mid + 1\n    var k = 0\n    // While both subarrays still have elements, compare and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++]\n        else\n            tmp[k++] = nums[j++]\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++]\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++]\n    }\n    // Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n    for (l in tmp.indices) {\n        nums[left + l] = tmp[l]\n    }\n}\n\n/* Merge sort */\nfun mergeSort(nums: IntArray, left: Int, right: Int) {\n    // Termination condition\n    if (left >= right) return  // Terminate recursion when subarray length is 1\n    // Divide and conquer stage\n    val mid = left + (right - left) / 2 // Calculate midpoint\n    mergeSort(nums, left, mid) // Recursively process the left subarray\n    mergeSort(nums, mid + 1, right) // Recursively process the right subarray\n    // Merge stage\n    merge(nums, left, mid, right)\n}\n
merge_sort.rb
### Merge left and right subarrays ###\ndef merge(nums, left, mid, right)\n  # Left subarray interval is [left, mid], right subarray interval is [mid+1, right]\n  # Create temporary array tmp to store merged result\n  tmp = Array.new(right - left + 1, 0)\n  # Initialize the start indices of the left and right subarrays\n  i, j, k = left, mid + 1, 0\n  # While both subarrays still have elements, compare and copy the smaller element into the temporary array\n  while i <= mid && j <= right\n    if nums[i] <= nums[j]\n      tmp[k] = nums[i]\n      i += 1\n    else\n      tmp[k] = nums[j]\n      j += 1\n    end\n    k += 1\n  end\n  # Copy the remaining elements of the left and right subarrays into the temporary array\n  while i <= mid\n    tmp[k] = nums[i]\n    i += 1\n    k += 1\n  end\n  while j <= right\n    tmp[k] = nums[j]\n    j += 1\n    k += 1\n  end\n  # Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval\n  (0...tmp.length).each do |k|\n    nums[left + k] = tmp[k]\n  end\nend\n\n### Merge sort ###\ndef merge_sort(nums, left, right)\n  # Termination condition\n  # Terminate recursion when subarray length is 1\n  return if left >= right\n  # Divide and conquer stage\n  mid = left + (right - left) / 2 # Calculate midpoint\n  merge_sort(nums, left, mid) # Recursively process the left subarray\n  merge_sort(nums, mid + 1, right) # Recursively process the right subarray\n  # Merge stage\n  merge(nums, left, mid, right)\nend\n
","path":["Chapter 11. Sorting","11.6   Merge Sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1162-algorithm-characteristics","level":2,"title":"11.6.2   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n \\log n)\\), non-adaptive sorting: The division produces a recursion tree of height \\(\\log n\\), and the total number of merge operations at each level is \\(n\\), so the overall time complexity is \\(O(n \\log n)\\).
  • Space complexity of \\(O(n)\\), non-in-place sorting: The recursion depth is \\(\\log n\\), using \\(O(\\log n)\\) size of stack frame space. The merge operation requires the aid of an auxiliary array, using \\(O(n)\\) size of additional space.
  • Stable sorting: In the merge process, the order of equal elements remains unchanged.
","path":["Chapter 11. Sorting","11.6   Merge Sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1163-linked-list-sorting","level":2,"title":"11.6.3   Linked List Sorting","text":"

For linked lists, merge sort has significant advantages over other sorting algorithms, and can optimize the space complexity of linked list sorting tasks to \\(O(1)\\).

  • Divide phase: \"Iteration\" can be used instead of \"recursion\" to implement linked list division work, thus saving the stack frame space used by recursion.
  • Merge phase: In linked lists, node insertion and deletion operations can be achieved by just changing references (pointers), so there is no need to create additional linked lists during the merge phase (merging two short ordered linked lists into one long ordered linked list).

The specific implementation details are quite complex, and interested readers can consult related materials for learning.

","path":["Chapter 11. Sorting","11.6   Merge Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/","level":1,"title":"11.5   Quick Sort","text":"

Quick sort (quick sort) is a sorting algorithm based on the divide-and-conquer strategy, which operates efficiently and is widely applied.

The core operation of quick sort is \"sentinel partitioning\", which aims to: select a certain element in the array as the \"pivot\", move all elements smaller than the pivot to its left, and move elements larger than the pivot to its right. Specifically, the flow of sentinel partitioning is shown in Figure 11-8.

  1. Select the leftmost element of the array as the pivot, and initialize two pointers i and j pointing to the two ends of the array.
  2. Set up a loop in which i (j) is used in each round to find the first element larger (smaller) than the pivot, and then swap these two elements.
  3. Loop through step 2. until i and j meet, and finally swap the pivot to the boundary line of the two sub-arrays.
<1><2><3><4><5><6><7><8><9>

Figure 11-8   Sentinel partitioning steps

After sentinel partitioning is complete, the original array is divided into three parts: left sub-array, pivot, right sub-array, satisfying \"any element in left sub-array \\(\\leq\\) pivot \\(\\leq\\) any element in right sub-array\". Therefore, we next only need to sort these two sub-arrays.

Divide-and-conquer strategy of quick sort

The essence of sentinel partitioning is to simplify the sorting problem of a longer array into the sorting problems of two shorter arrays.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
def partition(self, nums: list[int], left: int, right: int) -> int:\n    \"\"\"Sentinel partition\"\"\"\n    # Use nums[left] as the pivot\n    i, j = left, right\n    while i < j:\n        while i < j and nums[j] >= nums[left]:\n            j -= 1  # Search from right to left for the first element smaller than the pivot\n        while i < j and nums[i] <= nums[left]:\n            i += 1  # Search from left to right for the first element greater than the pivot\n        # Swap elements\n        nums[i], nums[j] = nums[j], nums[i]\n    # Swap the pivot to the boundary between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i  # Return the index of the pivot\n
quick_sort.cpp
/* Sentinel partition */\nint partition(vector<int> &nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;                // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;                // Search from left to right for the first element greater than the pivot\n        swap(nums[i], nums[j]); // Swap these two elements\n    }\n    swap(nums[i], nums[left]);  // Swap the pivot to the boundary between the two subarrays\n    return i;                   // Return the index of the pivot\n}\n
quick_sort.java
/* Swap elements */\nvoid swap(int[] nums, int i, int j) {\n    int tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Sentinel partition */\nint partition(int[] nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Search from left to right for the first element greater than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left);  // Swap the pivot to the boundary between the two subarrays\n    return i;             // Return the index of the pivot\n}\n
quick_sort.cs
/* Swap elements */\nvoid Swap(int[] nums, int i, int j) {\n    (nums[j], nums[i]) = (nums[i], nums[j]);\n}\n\n/* Sentinel partition */\nint Partition(int[] nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Search from left to right for the first element greater than the pivot\n        Swap(nums, i, j); // Swap these two elements\n    }\n    Swap(nums, i, left);  // Swap the pivot to the boundary between the two subarrays\n    return i;             // Return the index of the pivot\n}\n
quick_sort.go
/* Sentinel partition */\nfunc (q *quickSort) partition(nums []int, left, right int) int {\n    // Use nums[left] as the pivot\n    i, j := left, right\n    for i < j {\n        for i < j && nums[j] >= nums[left] {\n            j-- // Search from right to left for the first element smaller than the pivot\n        }\n        for i < j && nums[i] <= nums[left] {\n            i++ // Search from left to right for the first element greater than the pivot\n        }\n        // Swap elements\n        nums[i], nums[j] = nums[j], nums[i]\n    }\n    // Swap the pivot to the boundary between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i // Return the index of the pivot\n}\n
quick_sort.swift
/* Sentinel partition */\nfunc partition(nums: inout [Int], left: Int, right: Int) -> Int {\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while i < j {\n        while i < j, nums[j] >= nums[left] {\n            j -= 1 // Search from right to left for the first element smaller than the pivot\n        }\n        while i < j, nums[i] <= nums[left] {\n            i += 1 // Search from left to right for the first element greater than the pivot\n        }\n        nums.swapAt(i, j) // Swap these two elements\n    }\n    nums.swapAt(i, left) // Swap the pivot to the boundary between the two subarrays\n    return i // Return the index of the pivot\n}\n
quick_sort.js
/* Swap elements */\nswap(nums, i, j) {\n    let tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Sentinel partition */\npartition(nums, left, right) {\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j -= 1; // Search from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i += 1; // Search from left to right for the first element greater than the pivot\n        }\n        // Swap elements\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n    return i; // Return the index of the pivot\n}\n
quick_sort.ts
/* Swap elements */\nswap(nums: number[], i: number, j: number): void {\n    let tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Sentinel partition */\npartition(nums: number[], left: number, right: number): number {\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j -= 1; // Search from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i += 1; // Search from left to right for the first element greater than the pivot\n        }\n        // Swap elements\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n    return i; // Return the index of the pivot\n}\n
quick_sort.dart
/* Swap elements */\nvoid _swap(List<int> nums, int i, int j) {\n  int tmp = nums[i];\n  nums[i] = nums[j];\n  nums[j] = tmp;\n}\n\n/* Sentinel partition */\nint _partition(List<int> nums, int left, int right) {\n  // Use nums[left] as the pivot\n  int i = left, j = right;\n  while (i < j) {\n    while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot\n    while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot\n    _swap(nums, i, j); // Swap these two elements\n  }\n  _swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n  return i; // Return the index of the pivot\n}\n
quick_sort.rs
/* Sentinel partition */\nfn partition(nums: &mut [i32], left: usize, right: usize) -> usize {\n    // Use nums[left] as the pivot\n    let (mut i, mut j) = (left, right);\n    while i < j {\n        while i < j && nums[j] >= nums[left] {\n            j -= 1; // Search from right to left for the first element smaller than the pivot\n        }\n        while i < j && nums[i] <= nums[left] {\n            i += 1; // Search from left to right for the first element greater than the pivot\n        }\n        nums.swap(i, j); // Swap these two elements\n    }\n    nums.swap(i, left); // Swap the pivot to the boundary between the two subarrays\n    i // Return the index of the pivot\n}\n
quick_sort.c
/* Swap elements */\nvoid swap(int nums[], int i, int j) {\n    int tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Sentinel partition */\nint partition(int nums[], int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j--; // Search from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i++; // Search from left to right for the first element greater than the pivot\n        }\n        // Swap these two elements\n        swap(nums, i, j);\n    }\n    // Swap the pivot to the boundary between the two subarrays\n    swap(nums, i, left);\n    // Return the index of the pivot\n    return i;\n}\n
quick_sort.kt
/* Swap elements */\nfun swap(nums: IntArray, i: Int, j: Int) {\n    val temp = nums[i]\n    nums[i] = nums[j]\n    nums[j] = temp\n}\n\n/* Sentinel partition */\nfun partition(nums: IntArray, left: Int, right: Int): Int {\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--           // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++           // Search from left to right for the first element greater than the pivot\n        swap(nums, i, j)  // Swap these two elements\n    }\n    swap(nums, i, left)   // Swap the pivot to the boundary between the two subarrays\n    return i              // Return the index of the pivot\n}\n
quick_sort.rb
### Sentinel partition ###\ndef partition(nums, left, right)\n  # Use nums[left] as the pivot\n  i, j = left, right\n  while i < j\n    while i < j && nums[j] >= nums[left]\n      j -= 1 # Search from right to left for the first element smaller than the pivot\n    end\n    while i < j && nums[i] <= nums[left]\n      i += 1 # Search from left to right for the first element greater than the pivot\n    end\n    # Swap elements\n    nums[i], nums[j] = nums[j], nums[i]\n  end\n  # Swap the pivot to the boundary between the two subarrays\n  nums[i], nums[left] = nums[left], nums[i]\n  i # Return the index of the pivot\nend\n
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1151-algorithm-flow","level":2,"title":"11.5.1   Algorithm Flow","text":"

The overall flow of quick sort is shown in Figure 11-9.

  1. First, perform one \"sentinel partitioning\" on the original array to obtain the unsorted left sub-array and right sub-array.
  2. Then, recursively perform \"sentinel partitioning\" on the left sub-array and right sub-array respectively.
  3. Continue recursively until the sub-array length is 1, at which point sorting of the entire array is complete.

Figure 11-9   Quick sort flow

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
def quick_sort(self, nums: list[int], left: int, right: int):\n    \"\"\"Quick sort\"\"\"\n    # Terminate recursion when subarray length is 1\n    if left >= right:\n        return\n    # Sentinel partition\n    pivot = self.partition(nums, left, right)\n    # Recursively process the left subarray and right subarray\n    self.quick_sort(nums, left, pivot - 1)\n    self.quick_sort(nums, pivot + 1, right)\n
quick_sort.cpp
/* Quick sort */\nvoid quickSort(vector<int> &nums, int left, int right) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right)\n        return;\n    // Sentinel partition\n    int pivot = partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
quick_sort.java
/* Quick sort */\nvoid quickSort(int[] nums, int left, int right) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right)\n        return;\n    // Sentinel partition\n    int pivot = partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
quick_sort.cs
/* Quick sort */\nvoid QuickSort(int[] nums, int left, int right) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right)\n        return;\n    // Sentinel partition\n    int pivot = Partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    QuickSort(nums, left, pivot - 1);\n    QuickSort(nums, pivot + 1, right);\n}\n
quick_sort.go
/* Quick sort */\nfunc (q *quickSort) quickSort(nums []int, left, right int) {\n    // Terminate recursion when subarray length is 1\n    if left >= right {\n        return\n    }\n    // Sentinel partition\n    pivot := q.partition(nums, left, right)\n    // Recursively process the left subarray and right subarray\n    q.quickSort(nums, left, pivot-1)\n    q.quickSort(nums, pivot+1, right)\n}\n
quick_sort.swift
/* Quick sort */\nfunc quickSort(nums: inout [Int], left: Int, right: Int) {\n    // Terminate recursion when subarray length is 1\n    if left >= right {\n        return\n    }\n    // Sentinel partition\n    let pivot = partition(nums: &nums, left: left, right: right)\n    // Recursively process the left subarray and right subarray\n    quickSort(nums: &nums, left: left, right: pivot - 1)\n    quickSort(nums: &nums, left: pivot + 1, right: right)\n}\n
quick_sort.js
/* Quick sort */\nquickSort(nums, left, right) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right) return;\n    // Sentinel partition\n    const pivot = this.partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    this.quickSort(nums, left, pivot - 1);\n    this.quickSort(nums, pivot + 1, right);\n}\n
quick_sort.ts
/* Quick sort */\nquickSort(nums: number[], left: number, right: number): void {\n    // Terminate recursion when subarray length is 1\n    if (left >= right) {\n        return;\n    }\n    // Sentinel partition\n    const pivot = this.partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    this.quickSort(nums, left, pivot - 1);\n    this.quickSort(nums, pivot + 1, right);\n}\n
quick_sort.dart
/* Quick sort */\nvoid quickSort(List<int> nums, int left, int right) {\n  // Terminate recursion when subarray length is 1\n  if (left >= right) return;\n  // Sentinel partition\n  int pivot = _partition(nums, left, right);\n  // Recursively process the left subarray and right subarray\n  quickSort(nums, left, pivot - 1);\n  quickSort(nums, pivot + 1, right);\n}\n
quick_sort.rs
/* Quick sort */\npub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) {\n    // Terminate recursion when subarray length is 1\n    if left >= right {\n        return;\n    }\n    // Sentinel partition\n    let pivot = Self::partition(nums, left as usize, right as usize) as i32;\n    // Recursively process the left subarray and right subarray\n    Self::quick_sort(left, pivot - 1, nums);\n    Self::quick_sort(pivot + 1, right, nums);\n}\n
quick_sort.c
/* Quick sort */\nvoid quickSort(int nums[], int left, int right) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right) {\n        return;\n    }\n    // Sentinel partition\n    int pivot = partition(nums, left, right);\n    // Recursively process the left subarray and right subarray\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
quick_sort.kt
/* Quick sort */\nfun quickSort(nums: IntArray, left: Int, right: Int) {\n    // Terminate recursion when subarray length is 1\n    if (left >= right) return\n    // Sentinel partition\n    val pivot = partition(nums, left, right)\n    // Recursively process the left subarray and right subarray\n    quickSort(nums, left, pivot - 1)\n    quickSort(nums, pivot + 1, right)\n}\n
quick_sort.rb
### Quick sort class ###\ndef quick_sort(nums, left, right)\n  # Recurse when subarray length is not 1\n  if left < right\n    # Sentinel partition\n    pivot = partition(nums, left, right)\n    # Recursively process the left subarray and right subarray\n    quick_sort(nums, left, pivot - 1)\n    quick_sort(nums, pivot + 1, right)\n  end\n  nums\nend\n
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1152-algorithm-characteristics","level":2,"title":"11.5.2   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n \\log n)\\), non-adaptive sorting: In the average case, the number of recursive levels of sentinel partitioning is \\(\\log n\\), and the total number of loops at each level is \\(n\\), using \\(O(n \\log n)\\) time overall. In the worst case, each round of sentinel partitioning divides an array of length \\(n\\) into two sub-arrays of length \\(0\\) and \\(n - 1\\), at which point the number of recursive levels reaches \\(n\\), the number of loops at each level is \\(n\\), and the total time used is \\(O(n^2)\\).
  • Space complexity of \\(O(n)\\), in-place sorting: In the case where the input array is completely reversed, the worst recursive depth reaches \\(n\\), using \\(O(n)\\) stack frame space. The sorting operation is performed on the original array without the aid of an additional array.
  • Non-stable sorting: In the last step of sentinel partitioning, the pivot may be swapped to the right of equal elements.
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1153-why-is-quick-sort-fast","level":2,"title":"11.5.3   Why Is Quick Sort Fast","text":"

From the name, we can see that quick sort should have certain advantages in terms of efficiency. Although the average time complexity of quick sort is the same as \"merge sort\" and \"heap sort\", quick sort is usually more efficient, mainly for the following reasons.

  • The probability of the worst case occurring is very low: Although the worst-case time complexity of quick sort is \\(O(n^2)\\), which is not as stable as merge sort, in the vast majority of cases, quick sort can run with a time complexity of \\(O(n \\log n)\\).
  • High cache utilization: When performing sentinel partitioning operations, the system can load the entire sub-array into the cache, so element access efficiency is relatively high. Algorithms like \"heap sort\" require jump-style access to elements, thus lacking this characteristic.
  • Small constant coefficient of complexity: Among the three algorithms mentioned above, quick sort has the smallest total number of operations such as comparisons, assignments, and swaps. This is similar to the reason why \"insertion sort\" is faster than \"bubble sort\".
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1154-pivot-optimization","level":2,"title":"11.5.4   Pivot Optimization","text":"

Quick sort may have reduced time efficiency for certain inputs. Take an extreme example: suppose the input array is completely reversed. Since we select the leftmost element as the pivot, after sentinel partitioning is complete, the pivot is swapped to the rightmost end of the array, causing the left sub-array length to be \\(n - 1\\) and the right sub-array length to be \\(0\\). If we recurse down like this, each round of sentinel partitioning will have a sub-array length of \\(0\\), the divide-and-conquer strategy fails, and quick sort degrades to a form approximate to \"bubble sort\".

To avoid this situation as much as possible, we can optimize the pivot selection strategy in sentinel partitioning. For example, we can randomly select an element as the pivot. However, if luck is not good and we select a non-ideal pivot every time, efficiency is still not satisfactory.

It should be noted that programming languages usually generate \"pseudo-random numbers\". If we construct a specific test case for a pseudo-random number sequence, the efficiency of quick sort may still degrade.

For further improvement, we can select three candidate elements in the array (usually the first, last, and middle elements of the array), and use the median of these three candidate elements as the pivot. In this way, the probability that the pivot is \"neither too small nor too large\" will be greatly increased. Of course, we can also select more candidate elements to further improve the robustness of the algorithm. After adopting this method, the probability of time complexity degrading to \\(O(n^2)\\) is greatly reduced.

Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int:\n    \"\"\"Select the median of three candidate elements\"\"\"\n    l, m, r = nums[left], nums[mid], nums[right]\n    if (l <= m <= r) or (r <= m <= l):\n        return mid  # m is between l and r\n    if (m <= l <= r) or (r <= l <= m):\n        return left  # l is between m and r\n    return right\n\ndef partition(self, nums: list[int], left: int, right: int) -> int:\n    \"\"\"Sentinel partition (median of three)\"\"\"\n    # Use nums[left] as the pivot\n    med = self.median_three(nums, left, (left + right) // 2, right)\n    # Swap the median to the array's leftmost position\n    nums[left], nums[med] = nums[med], nums[left]\n    # Use nums[left] as the pivot\n    i, j = left, right\n    while i < j:\n        while i < j and nums[j] >= nums[left]:\n            j -= 1  # Search from right to left for the first element smaller than the pivot\n        while i < j and nums[i] <= nums[left]:\n            i += 1  # Search from left to right for the first element greater than the pivot\n        # Swap elements\n        nums[i], nums[j] = nums[j], nums[i]\n    # Swap the pivot to the boundary between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i  # Return the index of the pivot\n
quick_sort.cpp
/* Select the median of three candidate elements */\nint medianThree(vector<int> &nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m is between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l is between m and r\n    return right;\n}\n\n/* Sentinel partition (median of three) */\nint partition(vector<int> &nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median to the array's leftmost position\n    swap(nums[left], nums[med]);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;                // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;                // Search from left to right for the first element greater than the pivot\n        swap(nums[i], nums[j]); // Swap these two elements\n    }\n    swap(nums[i], nums[left]);  // Swap the pivot to the boundary between the two subarrays\n    return i;                   // Return the index of the pivot\n}\n
quick_sort.java
/* Select the median of three candidate elements */\nint medianThree(int[] nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m is between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l is between m and r\n    return right;\n}\n\n/* Sentinel partition (median of three) */\nint partition(int[] nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median to the array's leftmost position\n    swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Search from left to right for the first element greater than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left);  // Swap the pivot to the boundary between the two subarrays\n    return i;             // Return the index of the pivot\n}\n
quick_sort.cs
/* Select the median of three candidate elements */\nint MedianThree(int[] nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m is between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l is between m and r\n    return right;\n}\n\n/* Sentinel partition (median of three) */\nint Partition(int[] nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = MedianThree(nums, left, (left + right) / 2, right);\n    // Swap the median to the array's leftmost position\n    Swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Search from left to right for the first element greater than the pivot\n        Swap(nums, i, j); // Swap these two elements\n    }\n    Swap(nums, i, left);  // Swap the pivot to the boundary between the two subarrays\n    return i;             // Return the index of the pivot\n}\n
quick_sort.go
/* Select the median of three candidate elements */\nfunc (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int {\n    l, m, r := nums[left], nums[mid], nums[right]\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid // m is between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left // l is between m and r\n    }\n    return right\n}\n\n/* Sentinel partition (median of three) */\nfunc (q *quickSortMedian) partition(nums []int, left, right int) int {\n    // Use nums[left] as the pivot\n    med := q.medianThree(nums, left, (left+right)/2, right)\n    // Swap the median to the array's leftmost position\n    nums[left], nums[med] = nums[med], nums[left]\n    // Use nums[left] as the pivot\n    i, j := left, right\n    for i < j {\n        for i < j && nums[j] >= nums[left] {\n            j-- // Search from right to left for the first element smaller than the pivot\n        }\n        for i < j && nums[i] <= nums[left] {\n            i++ // Search from left to right for the first element greater than the pivot\n        }\n        // Swap elements\n        nums[i], nums[j] = nums[j], nums[i]\n    }\n    // Swap the pivot to the boundary between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i // Return the index of the pivot\n}\n
quick_sort.swift
/* Select the median of three candidate elements */\nfunc medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int {\n    let l = nums[left]\n    let m = nums[mid]\n    let r = nums[right]\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid // m is between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left // l is between m and r\n    }\n    return right\n}\n\n/* Sentinel partition (median of three) */\nfunc partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int {\n    // Select the median of three candidate elements\n    let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right)\n    // Swap the median to the array's leftmost position\n    nums.swapAt(left, med)\n    return partition(nums: &nums, left: left, right: right)\n}\n
quick_sort.js
/* Select the median of three candidate elements */\nmedianThree(nums, left, mid, right) {\n    let l = nums[left],\n        m = nums[mid],\n        r = nums[right];\n    // m is between l and r\n    if ((l <= m && m <= r) || (r <= m && m <= l)) return mid;\n    // l is between m and r\n    if ((m <= l && l <= r) || (r <= l && l <= m)) return left;\n    return right;\n}\n\n/* Sentinel partition (median of three) */\npartition(nums, left, right) {\n    // Select the median of three candidate elements\n    let med = this.medianThree(\n        nums,\n        left,\n        Math.floor((left + right) / 2),\n        right\n    );\n    // Swap the median to the array's leftmost position\n    this.swap(nums, left, med);\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n    return i; // Return the index of the pivot\n}\n
quick_sort.ts
/* Select the median of three candidate elements */\nmedianThree(\n    nums: number[],\n    left: number,\n    mid: number,\n    right: number\n): number {\n    let l = nums[left],\n        m = nums[mid],\n        r = nums[right];\n    // m is between l and r\n    if ((l <= m && m <= r) || (r <= m && m <= l)) return mid;\n    // l is between m and r\n    if ((m <= l && l <= r) || (r <= l && l <= m)) return left;\n    return right;\n}\n\n/* Sentinel partition (median of three) */\npartition(nums: number[], left: number, right: number): number {\n    // Select the median of three candidate elements\n    let med = this.medianThree(\n        nums,\n        left,\n        Math.floor((left + right) / 2),\n        right\n    );\n    // Swap the median to the array's leftmost position\n    this.swap(nums, left, med);\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j--; // Search from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i++; // Search from left to right for the first element greater than the pivot\n        }\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n    return i; // Return the index of the pivot\n}\n
quick_sort.dart
/* Select the median of three candidate elements */\nint _medianThree(List<int> nums, int left, int mid, int right) {\n  int l = nums[left], m = nums[mid], r = nums[right];\n  if ((l <= m && m <= r) || (r <= m && m <= l))\n    return mid; // m is between l and r\n  if ((m <= l && l <= r) || (r <= l && l <= m))\n    return left; // l is between m and r\n  return right;\n}\n\n/* Sentinel partition (median of three) */\nint _partition(List<int> nums, int left, int right) {\n  // Select the median of three candidate elements\n  int med = _medianThree(nums, left, (left + right) ~/ 2, right);\n  // Swap the median to the array's leftmost position\n  _swap(nums, left, med);\n  // Use nums[left] as the pivot\n  int i = left, j = right;\n  while (i < j) {\n    while (i < j && nums[j] >= nums[left]) j--; // Search from right to left for the first element smaller than the pivot\n    while (i < j && nums[i] <= nums[left]) i++; // Search from left to right for the first element greater than the pivot\n    _swap(nums, i, j); // Swap these two elements\n  }\n  _swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n  return i; // Return the index of the pivot\n}\n
quick_sort.rs
/* Select the median of three candidate elements */\nfn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize {\n    let (l, m, r) = (nums[left], nums[mid], nums[right]);\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid; // m is between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left; // l is between m and r\n    }\n    right\n}\n\n/* Sentinel partition (median of three) */\nfn partition(nums: &mut [i32], left: usize, right: usize) -> usize {\n    // Select the median of three candidate elements\n    let med = Self::median_three(nums, left, (left + right) / 2, right);\n    // Swap the median to the array's leftmost position\n    nums.swap(left, med);\n    // Use nums[left] as the pivot\n    let (mut i, mut j) = (left, right);\n    while i < j {\n        while i < j && nums[j] >= nums[left] {\n            j -= 1; // Search from right to left for the first element smaller than the pivot\n        }\n        while i < j && nums[i] <= nums[left] {\n            i += 1; // Search from left to right for the first element greater than the pivot\n        }\n        nums.swap(i, j); // Swap these two elements\n    }\n    nums.swap(i, left); // Swap the pivot to the boundary between the two subarrays\n    i // Return the index of the pivot\n}\n
quick_sort.c
/* Select the median of three candidate elements */\nint medianThree(int nums[], int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m is between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l is between m and r\n    return right;\n}\n\n/* Sentinel partition (median of three) */\nint partitionMedian(int nums[], int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median to the array's leftmost position\n    swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--; // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Search from left to right for the first element greater than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays\n    return i;            // Return the index of the pivot\n}\n
quick_sort.kt
/* Select the median of three candidate elements */\nfun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int {\n    val l = nums[left]\n    val m = nums[mid]\n    val r = nums[right]\n    if ((m in l..r) || (m in r..l))\n        return mid  // m is between l and r\n    if ((l in m..r) || (l in r..m))\n        return left // l is between m and r\n    return right\n}\n\n/* Sentinel partition (median of three) */\nfun partitionMedian(nums: IntArray, left: Int, right: Int): Int {\n    // Select the median of three candidate elements\n    val med = medianThree(nums, left, (left + right) / 2, right)\n    // Swap the median to the array's leftmost position\n    swap(nums, left, med)\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--                      // Search from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++                      // Search from left to right for the first element greater than the pivot\n        swap(nums, i, j)             // Swap these two elements\n    }\n    swap(nums, i, left)              // Swap the pivot to the boundary between the two subarrays\n    return i                         // Return the index of the pivot\n}\n
quick_sort.rb
### Select median of three candidate elements ###\ndef median_three(nums, left, mid, right)\n  # Select the median of three candidate elements\n  _l, _m, _r = nums[left], nums[mid], nums[right]\n  # m is between l and r\n  return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l)\n  # l is between m and r\n  return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m)\n  return right\nend\n\n### Sentinel partition (median of three) ###\ndef partition(nums, left, right)\n  ### Use nums[left] as pivot\n  med = median_three(nums, left, (left + right) / 2, right)\n  # Swap median to leftmost position of array\n  nums[left], nums[med] = nums[med], nums[left]\n  i, j = left, right\n  while i < j\n    while i < j && nums[j] >= nums[left]\n      j -= 1 # Search from right to left for the first element smaller than the pivot\n    end\n    while i < j && nums[i] <= nums[left]\n      i += 1 # Search from left to right for the first element greater than the pivot\n    end\n    # Swap elements\n    nums[i], nums[j] = nums[j], nums[i]\n  end\n  # Swap the pivot to the boundary between the two subarrays\n  nums[i], nums[left] = nums[left], nums[i]\n  i # Return the index of the pivot\nend\n
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1155-recursive-depth-optimization","level":2,"title":"11.5.5   Recursive Depth Optimization","text":"

For certain inputs, quick sort may occupy more space. Taking a completely ordered input array as an example, let the length of the sub-array in recursion be \\(m\\). Each round of sentinel partitioning will produce a left sub-array of length \\(0\\) and a right sub-array of length \\(m - 1\\), which means that the problem scale reduced per recursive call is very small (only one element is reduced), and the height of the recursion tree will reach \\(n - 1\\), at which point \\(O(n)\\) size of stack frame space is required.

To prevent the accumulation of stack frame space, we can compare the lengths of the two sub-arrays after each round of sentinel sorting is complete, and only recurse on the shorter sub-array. Since the length of the shorter sub-array will not exceed \\(n / 2\\), this method can ensure that the recursion depth does not exceed \\(\\log n\\), thus optimizing the worst-case space complexity to \\(O(\\log n)\\). The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
def quick_sort(self, nums: list[int], left: int, right: int):\n    \"\"\"Quick sort (recursion depth optimization)\"\"\"\n    # Terminate when subarray length is 1\n    while left < right:\n        # Sentinel partition operation\n        pivot = self.partition(nums, left, right)\n        # Perform quick sort on the shorter of the two subarrays\n        if pivot - left < right - pivot:\n            self.quick_sort(nums, left, pivot - 1)  # Recursively sort the left subarray\n            left = pivot + 1  # Remaining unsorted interval is [pivot + 1, right]\n        else:\n            self.quick_sort(nums, pivot + 1, right)  # Recursively sort the right subarray\n            right = pivot - 1  # Remaining unsorted interval is [left, pivot - 1]\n
quick_sort.cpp
/* Quick sort (recursion depth optimization) */\nvoid quickSort(vector<int> &nums, int left, int right) {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        int pivot = partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1;                 // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1;                 // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.java
/* Quick sort (recursion depth optimization) */\nvoid quickSort(int[] nums, int left, int right) {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        int pivot = partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.cs
/* Quick sort (recursion depth optimization) */\nvoid QuickSort(int[] nums, int left, int right) {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        int pivot = Partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            QuickSort(nums, left, pivot - 1);  // Recursively sort the left subarray\n            left = pivot + 1;  // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            QuickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.go
/* Quick sort (recursion depth optimization) */\nfunc (q *quickSortTailCall) quickSort(nums []int, left, right int) {\n    // Terminate when subarray length is 1\n    for left < right {\n        // Sentinel partition operation\n        pivot := q.partition(nums, left, right)\n        // Perform quick sort on the shorter of the two subarrays\n        if pivot-left < right-pivot {\n            q.quickSort(nums, left, pivot-1) // Recursively sort the left subarray\n            left = pivot + 1                 // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            q.quickSort(nums, pivot+1, right) // Recursively sort the right subarray\n            right = pivot - 1                 // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.swift
/* Quick sort (recursion depth optimization) */\nfunc quickSortTailCall(nums: inout [Int], left: Int, right: Int) {\n    var left = left\n    var right = right\n    // Terminate when subarray length is 1\n    while left < right {\n        // Sentinel partition operation\n        let pivot = partition(nums: &nums, left: left, right: right)\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left) < (right - pivot) {\n            quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // Recursively sort the left subarray\n            left = pivot + 1 // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // Recursively sort the right subarray\n            right = pivot - 1 // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.js
/* Quick sort (recursion depth optimization) */\nquickSort(nums, left, right) {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        let pivot = this.partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            this.quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            this.quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.ts
/* Quick sort (recursion depth optimization) */\nquickSort(nums: number[], left: number, right: number): void {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        let pivot = this.partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            this.quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            this.quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.dart
/* Quick sort (recursion depth optimization) */\nvoid quickSort(List<int> nums, int left, int right) {\n  // Terminate when subarray length is 1\n  while (left < right) {\n    // Sentinel partition operation\n    int pivot = _partition(nums, left, right);\n    // Perform quick sort on the shorter of the two subarrays\n    if (pivot - left < right - pivot) {\n      quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n      left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]\n    } else {\n      quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n      right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n    }\n  }\n}\n
quick_sort.rs
/* Quick sort (recursion depth optimization) */\npub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) {\n    // Terminate when subarray length is 1\n    while left < right {\n        // Sentinel partition operation\n        let pivot = Self::partition(nums, left as usize, right as usize) as i32;\n        // Perform quick sort on the shorter of the two subarrays\n        if pivot - left < right - pivot {\n            Self::quick_sort(left, pivot - 1, nums); // Recursively sort the left subarray\n            left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            Self::quick_sort(pivot + 1, right, nums); // Recursively sort the right subarray\n            right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.c
/* Quick sort (recursion depth optimization) */\nvoid quickSortTailCall(int nums[], int left, int right) {\n    // Terminate when subarray length is 1\n    while (left < right) {\n        // Sentinel partition operation\n        int pivot = partition(nums, left, right);\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            // Recursively sort the left subarray\n            quickSortTailCall(nums, left, pivot - 1);\n            // Remaining unsorted interval is [pivot + 1, right]\n            left = pivot + 1;\n        } else {\n            // Recursively sort the right subarray\n            quickSortTailCall(nums, pivot + 1, right);\n            // Remaining unsorted interval is [left, pivot - 1]\n            right = pivot - 1;\n        }\n    }\n}\n
quick_sort.kt
/* Quick sort (recursion depth optimization) */\nfun quickSortTailCall(nums: IntArray, left: Int, right: Int) {\n    // Terminate when subarray length is 1\n    var l = left\n    var r = right\n    while (l < r) {\n        // Sentinel partition operation\n        val pivot = partition(nums, l, r)\n        // Perform quick sort on the shorter of the two subarrays\n        if (pivot - l < r - pivot) {\n            quickSort(nums, l, pivot - 1) // Recursively sort the left subarray\n            l = pivot + 1 // Remaining unsorted interval is [pivot + 1, right]\n        } else {\n            quickSort(nums, pivot + 1, r) // Recursively sort the right subarray\n            r = pivot - 1 // Remaining unsorted interval is [left, pivot - 1]\n        }\n    }\n}\n
quick_sort.rb
### Quick sort (recursion depth optimization) ###\ndef quick_sort(nums, left, right)\n  # Recurse when subarray length is not 1\n  while left < right\n    # Sentinel partition\n    pivot = partition(nums, left, right)\n    # Perform quick sort on the shorter of the two subarrays\n    if pivot - left < right - pivot\n      quick_sort(nums, left, pivot - 1)\n      left = pivot + 1 # Remaining unsorted interval is [pivot + 1, right]\n    else\n      quick_sort(nums, pivot + 1, right)\n      right = pivot - 1 # Remaining unsorted interval is [left, pivot - 1]\n    end\n  end\nend\n
","path":["Chapter 11. Sorting","11.5   Quick Sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/","level":1,"title":"11.10   Radix Sort","text":"

The previous section introduced counting sort, which is suitable for situations where the data volume \\(n\\) is large but the data range \\(m\\) is small. Suppose we need to sort \\(n = 10^6\\) student IDs, and the student ID is an 8-digit number, which means the data range \\(m = 10^8\\) is very large. Using counting sort would require allocating a large amount of memory space, whereas radix sort can avoid this situation.

Radix sort (radix sort) has a core idea consistent with counting sort, which also achieves sorting by counting quantities. Building on this, radix sort utilizes the progressive relationship between the digits of numbers, sorting each digit in turn to obtain the final sorting result.

","path":["Chapter 11. Sorting","11.10   Radix Sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/#11101-algorithm-flow","level":2,"title":"11.10.1   Algorithm Flow","text":"

Taking student ID data as an example, assume the lowest digit is the \\(1\\)st digit and the highest digit is the \\(8\\)th digit. The flow of radix sort is shown in Figure 11-18.

  1. Initialize the digit \\(k = 1\\).
  2. Perform \"counting sort\" on the \\(k\\)th digit of the student IDs. After completion, the data will be sorted from smallest to largest according to the \\(k\\)th digit.
  3. Increase \\(k\\) by \\(1\\), then return to step 2. and continue iterating until all digits are sorted, at which point the process ends.

Figure 11-18   Radix sort algorithm flow

Below we analyze the code implementation. For a \\(d\\)-base number \\(x\\), to get its \\(k\\)th digit \\(x_k\\), the following calculation formula can be used:

\\[ x_k = \\lfloor\\frac{x}{d^{k-1}}\\rfloor \\bmod d \\]

Where \\(\\lfloor a \\rfloor\\) denotes rounding down the floating-point number \\(a\\), and \\(\\bmod \\: d\\) denotes taking the modulo (remainder) with respect to \\(d\\). For student ID data, \\(d = 10\\) and \\(k \\in [1, 8]\\).

Additionally, we need to slightly modify the counting sort code to make it sort based on the \\(k\\)th digit of the number:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby radix_sort.py
def digit(num: int, exp: int) -> int:\n    \"\"\"Get the k-th digit of element num, where exp = 10^(k-1)\"\"\"\n    # Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num // exp) % 10\n\ndef counting_sort_digit(nums: list[int], exp: int):\n    \"\"\"Counting sort (based on nums k-th digit)\"\"\"\n    # Decimal digit range is 0~9, therefore need a bucket array of length 10\n    counter = [0] * 10\n    n = len(nums)\n    # Count the occurrence of digits 0~9\n    for i in range(n):\n        d = digit(nums[i], exp)  # Get the k-th digit of nums[i], noted as d\n        counter[d] += 1  # Count the occurrence of digit d\n    # Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for i in range(1, 10):\n        counter[i] += counter[i - 1]\n    # Traverse in reverse, based on bucket statistics, place each element into res\n    res = [0] * n\n    for i in range(n - 1, -1, -1):\n        d = digit(nums[i], exp)\n        j = counter[d] - 1  # Get the index j for d in the array\n        res[j] = nums[i]  # Place the current element at index j\n        counter[d] -= 1  # Decrease the count of d by 1\n    # Use result to overwrite the original array nums\n    for i in range(n):\n        nums[i] = res[i]\n\ndef radix_sort(nums: list[int]):\n    \"\"\"Radix sort\"\"\"\n    # Get the maximum element of the array, used to determine the maximum number of digits\n    m = max(nums)\n    # Traverse from the lowest to the highest digit\n    exp = 1\n    while exp <= m:\n        # Perform counting sort on the k-th digit of array elements\n        # k = 1 -> exp = 1\n        # k = 2 -> exp = 10\n        # i.e., exp = 10^(k-1)\n        counting_sort_digit(nums, exp)\n        exp *= 10\n
radix_sort.cpp
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nvoid countingSortDigit(vector<int> &nums, int exp) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    vector<int> counter(10, 0);\n    int n = nums.size();\n    // Count the occurrence of digits 0~9\n    for (int i = 0; i < n; i++) {\n        int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d]++;                // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    vector<int> res(n, 0);\n    for (int i = n - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (int i = 0; i < n; i++)\n        nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(vector<int> &nums) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    int m = *max_element(nums.begin(), nums.end());\n    // Traverse from the lowest to the highest digit\n    for (int exp = 1; exp <= m; exp *= 10)\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n}\n
radix_sort.java
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nvoid countingSortDigit(int[] nums, int exp) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    int[] counter = new int[10];\n    int n = nums.length;\n    // Count the occurrence of digits 0~9\n    for (int i = 0; i < n; i++) {\n        int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d]++;                // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (int i = 0; i < n; i++)\n        nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(int[] nums) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    int m = Integer.MIN_VALUE;\n    for (int num : nums)\n        if (num > m)\n            m = num;\n    // Traverse from the lowest to the highest digit\n    for (int exp = 1; exp <= m; exp *= 10) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
radix_sort.cs
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nint Digit(int num, int exp) {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nvoid CountingSortDigit(int[] nums, int exp) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    int[] counter = new int[10];\n    int n = nums.Length;\n    // Count the occurrence of digits 0~9\n    for (int i = 0; i < n; i++) {\n        int d = Digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d]++;                // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int d = Digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nvoid RadixSort(int[] nums) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    int m = int.MinValue;\n    foreach (int num in nums) {\n        if (num > m) m = num;\n    }\n    // Traverse from the lowest to the highest digit\n    for (int exp = 1; exp <= m; exp *= 10) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        CountingSortDigit(nums, exp);\n    }\n}\n
radix_sort.go
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfunc digit(num, exp int) int {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10\n}\n\n/* Counting sort (based on nums k-th digit) */\nfunc countingSortDigit(nums []int, exp int) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    counter := make([]int, 10)\n    n := len(nums)\n    // Count the occurrence of digits 0~9\n    for i := 0; i < n; i++ {\n        d := digit(nums[i], exp) // Get the k-th digit of nums[i], noted as d\n        counter[d]++             // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for i := 1; i < 10; i++ {\n        counter[i] += counter[i-1]\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    res := make([]int, n)\n    for i := n - 1; i >= 0; i-- {\n        d := digit(nums[i], exp)\n        j := counter[d] - 1 // Get the index j for d in the array\n        res[j] = nums[i]    // Place the current element at index j\n        counter[d]--        // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for i := 0; i < n; i++ {\n        nums[i] = res[i]\n    }\n}\n\n/* Radix sort */\nfunc radixSort(nums []int) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    max := math.MinInt\n    for _, num := range nums {\n        if num > max {\n            max = num\n        }\n    }\n    // Traverse from the lowest to the highest digit\n    for exp := 1; max >= exp; exp *= 10 {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp)\n    }\n}\n
radix_sort.swift
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfunc digit(num: Int, exp: Int) -> Int {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    (num / exp) % 10\n}\n\n/* Counting sort (based on nums k-th digit) */\nfunc countingSortDigit(nums: inout [Int], exp: Int) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    var counter = Array(repeating: 0, count: 10)\n    // Count the occurrence of digits 0~9\n    for i in nums.indices {\n        let d = digit(num: nums[i], exp: exp) // Get the k-th digit of nums[i], noted as d\n        counter[d] += 1 // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for i in 1 ..< 10 {\n        counter[i] += counter[i - 1]\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    var res = Array(repeating: 0, count: nums.count)\n    for i in nums.indices.reversed() {\n        let d = digit(num: nums[i], exp: exp)\n        let j = counter[d] - 1 // Get the index j for d in the array\n        res[j] = nums[i] // Place the current element at index j\n        counter[d] -= 1 // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for i in nums.indices {\n        nums[i] = res[i]\n    }\n}\n\n/* Radix sort */\nfunc radixSort(nums: inout [Int]) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    var m = Int.min\n    for num in nums {\n        if num > m {\n            m = num\n        }\n    }\n    // Traverse from the lowest to the highest digit\n    for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums: &nums, exp: exp)\n    }\n}\n
radix_sort.js
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfunction digit(num, exp) {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return Math.floor(num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nfunction countingSortDigit(nums, exp) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    const counter = new Array(10).fill(0);\n    const n = nums.length;\n    // Count the occurrence of digits 0~9\n    for (let i = 0; i < n; i++) {\n        const d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d]++; // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (let i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    const res = new Array(n).fill(0);\n    for (let i = n - 1; i >= 0; i--) {\n        const d = digit(nums[i], exp);\n        const j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d]--; // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nfunction radixSort(nums) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    let m = Math.max(... nums);\n    // Traverse from the lowest to the highest digit\n    for (let exp = 1; exp <= m; exp *= 10) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
radix_sort.ts
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfunction digit(num: number, exp: number): number {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return Math.floor(num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nfunction countingSortDigit(nums: number[], exp: number): void {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    const counter = new Array(10).fill(0);\n    const n = nums.length;\n    // Count the occurrence of digits 0~9\n    for (let i = 0; i < n; i++) {\n        const d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d]++; // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (let i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    const res = new Array(n).fill(0);\n    for (let i = n - 1; i >= 0; i--) {\n        const d = digit(nums[i], exp);\n        const j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d]--; // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nfunction radixSort(nums: number[]): void {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    let m: number = Math.max(... nums);\n    // Traverse from the lowest to the highest digit\n    for (let exp = 1; exp <= m; exp *= 10) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
radix_sort.dart
/* Get k-th digit of element _num, where exp = 10^(k-1) */\nint digit(int _num, int exp) {\n  // Passing exp instead of k can avoid repeated expensive exponentiation here\n  return (_num ~/ exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nvoid countingSortDigit(List<int> nums, int exp) {\n  // Decimal digit range is 0~9, therefore need a bucket array of length 10\n  List<int> counter = List<int>.filled(10, 0);\n  int n = nums.length;\n  // Count the occurrence of digits 0~9\n  for (int i = 0; i < n; i++) {\n    int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n    counter[d]++; // Count the occurrence of digit d\n  }\n  // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n  for (int i = 1; i < 10; i++) {\n    counter[i] += counter[i - 1];\n  }\n  // Traverse in reverse, based on bucket statistics, place each element into res\n  List<int> res = List<int>.filled(n, 0);\n  for (int i = n - 1; i >= 0; i--) {\n    int d = digit(nums[i], exp);\n    int j = counter[d] - 1; // Get the index j for d in the array\n    res[j] = nums[i]; // Place the current element at index j\n    counter[d]--; // Decrease the count of d by 1\n  }\n  // Use result to overwrite the original array nums\n  for (int i = 0; i < n; i++) nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(List<int> nums) {\n  // Get the maximum element of the array, used to determine the maximum number of digits\n  // In Dart, int length is 64 bits\n  int m = -1 << 63;\n  for (int _num in nums) if (_num > m) m = _num;\n  // Traverse from the lowest to the highest digit\n  for (int exp = 1; exp <= m; exp *= 10)\n    // Perform counting sort on the k-th digit of array elements\n    // k = 1 -> exp = 1\n    // k = 2 -> exp = 10\n    // i.e., exp = 10^(k-1)\n    countingSortDigit(nums, exp);\n}\n
radix_sort.rs
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfn digit(num: i32, exp: i32) -> usize {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return ((num / exp) % 10) as usize;\n}\n\n/* Counting sort (based on nums k-th digit) */\nfn counting_sort_digit(nums: &mut [i32], exp: i32) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    let mut counter = [0; 10];\n    let n = nums.len();\n    // Count the occurrence of digits 0~9\n    for i in 0..n {\n        let d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d\n        counter[d] += 1; // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for i in 1..10 {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    let mut res = vec![0; n];\n    for i in (0..n).rev() {\n        let d = digit(nums[i], exp);\n        let j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d] -= 1; // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    nums.copy_from_slice(&res);\n}\n\n/* Radix sort */\nfn radix_sort(nums: &mut [i32]) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    let m = *nums.into_iter().max().unwrap();\n    // Traverse from the lowest to the highest digit\n    let mut exp = 1;\n    while exp <= m {\n        counting_sort_digit(nums, exp);\n        exp *= 10;\n    }\n}\n
radix_sort.c
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (based on nums k-th digit) */\nvoid countingSortDigit(int nums[], int size, int exp) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    int *counter = (int *)malloc((sizeof(int) * 10));\n    memset(counter, 0, sizeof(int) * 10); // Initialize to 0 to support subsequent memory release\n    // Count the occurrence of digits 0~9\n    for (int i = 0; i < size; i++) {\n        // Get the k-th digit of nums[i], noted as d\n        int d = digit(nums[i], exp);\n        // Count the occurrence of digit d\n        counter[d]++;\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    int *res = (int *)malloc(sizeof(int) * size);\n    for (int i = size - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the index j for d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (int i = 0; i < size; i++) {\n        nums[i] = res[i];\n    }\n    // Free memory\n    free(res);\n    free(counter);\n}\n\n/* Radix sort */\nvoid radixSort(int nums[], int size) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    int max = INT32_MIN;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > max) {\n            max = nums[i];\n        }\n    }\n    // Traverse from the lowest to the highest digit\n    for (int exp = 1; max >= exp; exp *= 10)\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, size, exp);\n}\n
radix_sort.kt
/* Get the k-th digit of element num, where exp = 10^(k-1) */\nfun digit(num: Int, exp: Int): Int {\n    // Passing exp instead of k can avoid repeated expensive exponentiation here\n    return (num / exp) % 10\n}\n\n/* Counting sort (based on nums k-th digit) */\nfun countingSortDigit(nums: IntArray, exp: Int) {\n    // Decimal digit range is 0~9, therefore need a bucket array of length 10\n    val counter = IntArray(10)\n    val n = nums.size\n    // Count the occurrence of digits 0~9\n    for (i in 0..<n) {\n        val d = digit(nums[i], exp) // Get the k-th digit of nums[i], noted as d\n        counter[d]++                // Count the occurrence of digit d\n    }\n    // Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n    for (i in 1..9) {\n        counter[i] += counter[i - 1]\n    }\n    // Traverse in reverse, based on bucket statistics, place each element into res\n    val res = IntArray(n)\n    for (i in n - 1 downTo 0) {\n        val d = digit(nums[i], exp)\n        val j = counter[d] - 1 // Get the index j for d in the array\n        res[j] = nums[i]       // Place the current element at index j\n        counter[d]--           // Decrease the count of d by 1\n    }\n    // Use result to overwrite the original array nums\n    for (i in 0..<n)\n        nums[i] = res[i]\n}\n\n/* Radix sort */\nfun radixSort(nums: IntArray) {\n    // Get the maximum element of the array, used to determine the maximum number of digits\n    var m = Int.MIN_VALUE\n    for (num in nums) if (num > m) m = num\n    var exp = 1\n    // Traverse from the lowest to the highest digit\n    while (exp <= m) {\n        // Perform counting sort on the k-th digit of array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // i.e., exp = 10^(k-1)\n        countingSortDigit(nums, exp)\n        exp *= 10\n    }\n}\n
radix_sort.rb
### Get k-th digit of element num, where exp = 10^(k-1) ###\ndef digit(num, exp)\n  # Passing exp instead of k avoids expensive exponentiation calculations\n  (num / exp) % 10\nend\n\n### Counting sort (sort by k-th digit of nums) ###\ndef counting_sort_digit(nums, exp)\n  # Decimal digit range is 0~9, therefore need a bucket array of length 10\n  counter = Array.new(10, 0)\n  n = nums.length\n  # Count the occurrence of digits 0~9\n  for i in 0...n\n    d = digit(nums[i], exp) # Get the k-th digit of nums[i], noted as d\n    counter[d] += 1 # Count the occurrence of digit d\n  end\n  # Calculate prefix sum, converting \"occurrence count\" into \"array index\"\n  (1...10).each { |i| counter[i] += counter[i - 1] }\n  # Traverse in reverse, based on bucket statistics, place each element into res\n  res = Array.new(n, 0)\n  for i in (n - 1).downto(0)\n    d = digit(nums[i], exp)\n    j = counter[d] - 1 # Get the index j for d in the array\n    res[j] = nums[i] # Place the current element at index j\n    counter[d] -= 1 # Decrease the count of d by 1\n  end\n  # Use result to overwrite the original array nums\n  (0...n).each { |i| nums[i] = res[i] }\nend\n\n### Radix sort ###\ndef radix_sort(nums)\n  # Get the maximum element of the array, used to determine the maximum number of digits\n  m = nums.max\n  # Traverse from the lowest to the highest digit\n  exp = 1\n  while exp <= m\n    # Perform counting sort on the k-th digit of array elements\n    # k = 1 -> exp = 1\n    # k = 2 -> exp = 10\n    # i.e., exp = 10^(k-1)\n    counting_sort_digit(nums, exp)\n    exp *= 10\n  end\nend\n

Why start sorting from the lowest digit?

In successive sorting rounds, the result of a later round will override the result of an earlier round. For example, if the first round result is \\(a < b\\), while the second round result is \\(a > b\\), then the second round's result will replace the first round's result. Since higher-order digits have higher priority than lower-order digits, we should sort the lower digits first and then sort the higher digits.

","path":["Chapter 11. Sorting","11.10   Radix Sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/#11102-algorithm-characteristics","level":2,"title":"11.10.2   Algorithm Characteristics","text":"

Compared to counting sort, radix sort is suitable for larger numerical ranges, but the prerequisite is that the data must be representable in a fixed number of digits, and the number of digits should not be too large. For example, floating-point numbers are not suitable for radix sort because their number of digits \\(k\\) may be too large, potentially leading to time complexity \\(O(nk) \\gg O(n^2)\\).

  • Time complexity of \\(O(nk)\\), non-adaptive sorting: Let the data volume be \\(n\\), the data be in base \\(d\\), and the maximum number of digits be \\(k\\). Then performing counting sort on a certain digit uses \\(O(n + d)\\) time, and sorting all \\(k\\) digits uses \\(O((n + d)k)\\) time. Typically, both \\(d\\) and \\(k\\) are relatively small, and the time complexity approaches \\(O(n)\\).
  • Space complexity of \\(O(n + d)\\), non-in-place sorting: Same as counting sort, radix sort requires auxiliary arrays res and counter of lengths \\(n\\) and \\(d\\).
  • Stable sorting: When counting sort is stable, radix sort is also stable; when counting sort is unstable, radix sort cannot guarantee obtaining correct sorting results.
","path":["Chapter 11. Sorting","11.10   Radix Sort"],"tags":[]},{"location":"chapter_sorting/selection_sort/","level":1,"title":"11.2   Selection Sort","text":"

Selection sort (selection sort) works very simply: it opens a loop, and in each round, selects the smallest element from the unsorted interval and places it at the end of the sorted interval.

Assume the array has length \\(n\\). The algorithm flow of selection sort is shown in Figure 11-2.

  1. Initially, all elements are unsorted, i.e., the unsorted (index) interval is \\([0, n-1]\\).
  2. Select the smallest element in the interval \\([0, n-1]\\) and swap it with the element at index \\(0\\). After completion, the first element of the array is sorted.
  3. Select the smallest element in the interval \\([1, n-1]\\) and swap it with the element at index \\(1\\). After completion, the first 2 elements of the array are sorted.
  4. And so on. After \\(n - 1\\) rounds of selection and swapping, the first \\(n - 1\\) elements of the array are sorted.
  5. The only remaining element must be the largest element, requiring no sorting, so the array sorting is complete.
<1><2><3><4><5><6><7><8><9><10><11>

Figure 11-2   Selection sort steps

In the code, we use \\(k\\) to record the smallest element within the unsorted interval:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby selection_sort.py
def selection_sort(nums: list[int]):\n    \"\"\"Selection sort\"\"\"\n    n = len(nums)\n    # Outer loop: unsorted interval is [i, n-1]\n    for i in range(n - 1):\n        # Inner loop: find the smallest element within the unsorted interval\n        k = i\n        for j in range(i + 1, n):\n            if nums[j] < nums[k]:\n                k = j  # Record the index of the smallest element\n        # Swap the smallest element with the first element of the unsorted interval\n        nums[i], nums[k] = nums[k], nums[i]\n
selection_sort.cpp
/* Selection sort */\nvoid selectionSort(vector<int> &nums) {\n    int n = nums.size();\n    // Outer loop: unsorted interval is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        swap(nums[i], nums[k]);\n    }\n}\n
selection_sort.java
/* Selection sort */\nvoid selectionSort(int[] nums) {\n    int n = nums.length;\n    // Outer loop: unsorted interval is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        int temp = nums[i];\n        nums[i] = nums[k];\n        nums[k] = temp;\n    }\n}\n
selection_sort.cs
/* Selection sort */\nvoid SelectionSort(int[] nums) {\n    int n = nums.Length;\n    // Outer loop: unsorted interval is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        (nums[k], nums[i]) = (nums[i], nums[k]);\n    }\n}\n
selection_sort.go
/* Selection sort */\nfunc selectionSort(nums []int) {\n    n := len(nums)\n    // Outer loop: unsorted interval is [i, n-1]\n    for i := 0; i < n-1; i++ {\n        // Inner loop: find the smallest element within the unsorted interval\n        k := i\n        for j := i + 1; j < n; j++ {\n            if nums[j] < nums[k] {\n                // Record the index of the smallest element\n                k = j\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        nums[i], nums[k] = nums[k], nums[i]\n\n    }\n}\n
selection_sort.swift
/* Selection sort */\nfunc selectionSort(nums: inout [Int]) {\n    // Outer loop: unsorted interval is [i, n-1]\n    for i in nums.indices.dropLast() {\n        // Inner loop: find the smallest element within the unsorted interval\n        var k = i\n        for j in nums.indices.dropFirst(i + 1) {\n            if nums[j] < nums[k] {\n                k = j // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        nums.swapAt(i, k)\n    }\n}\n
selection_sort.js
/* Selection sort */\nfunction selectionSort(nums) {\n    let n = nums.length;\n    // Outer loop: unsorted interval is [i, n-1]\n    for (let i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        let k = i;\n        for (let j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k]) {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        [nums[i], nums[k]] = [nums[k], nums[i]];\n    }\n}\n
selection_sort.ts
/* Selection sort */\nfunction selectionSort(nums: number[]): void {\n    let n = nums.length;\n    // Outer loop: unsorted interval is [i, n-1]\n    for (let i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        let k = i;\n        for (let j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k]) {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        [nums[i], nums[k]] = [nums[k], nums[i]];\n    }\n}\n
selection_sort.dart
/* Selection sort */\nvoid selectionSort(List<int> nums) {\n  int n = nums.length;\n  // Outer loop: unsorted interval is [i, n-1]\n  for (int i = 0; i < n - 1; i++) {\n    // Inner loop: find the smallest element within the unsorted interval\n    int k = i;\n    for (int j = i + 1; j < n; j++) {\n      if (nums[j] < nums[k]) k = j; // Record the index of the smallest element\n    }\n    // Swap the smallest element with the first element of the unsorted interval\n    int temp = nums[i];\n    nums[i] = nums[k];\n    nums[k] = temp;\n  }\n}\n
selection_sort.rs
/* Selection sort */\nfn selection_sort(nums: &mut [i32]) {\n    if nums.is_empty() {\n        return;\n    }\n    let n = nums.len();\n    // Outer loop: unsorted interval is [i, n-1]\n    for i in 0..n - 1 {\n        // Inner loop: find the smallest element within the unsorted interval\n        let mut k = i;\n        for j in i + 1..n {\n            if nums[j] < nums[k] {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        nums.swap(i, k);\n    }\n}\n
selection_sort.c
/* Selection sort */\nvoid selectionSort(int nums[], int n) {\n    // Outer loop: unsorted interval is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element within the unsorted interval\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        int temp = nums[i];\n        nums[i] = nums[k];\n        nums[k] = temp;\n    }\n}\n
selection_sort.kt
/* Selection sort */\nfun selectionSort(nums: IntArray) {\n    val n = nums.size\n    // Outer loop: unsorted interval is [i, n-1]\n    for (i in 0..<n - 1) {\n        var k = i\n        // Inner loop: find the smallest element within the unsorted interval\n        for (j in i + 1..<n) {\n            if (nums[j] < nums[k])\n                k = j // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted interval\n        val temp = nums[i]\n        nums[i] = nums[k]\n        nums[k] = temp\n    }\n}\n
selection_sort.rb
### Selection sort ###\ndef selection_sort(nums)\n  n = nums.length\n  # Outer loop: unsorted interval is [i, n-1]\n  for i in 0...(n - 1)\n    # Inner loop: find the smallest element within the unsorted interval\n    k = i\n    for j in (i + 1)...n\n      if nums[j] < nums[k]\n        k = j # Record the index of the smallest element\n      end\n    end\n    # Swap the smallest element with the first element of the unsorted interval\n    nums[i], nums[k] = nums[k], nums[i]\n  end\nend\n
","path":["Chapter 11. Sorting","11.2   Selection Sort"],"tags":[]},{"location":"chapter_sorting/selection_sort/#1121-algorithm-characteristics","level":2,"title":"11.2.1   Algorithm Characteristics","text":"
  • Time complexity of \\(O(n^2)\\), non-adaptive sorting: The outer loop has \\(n - 1\\) rounds in total. The length of the unsorted interval in the first round is \\(n\\), and the length of the unsorted interval in the last round is \\(2\\). That is, each round of the outer loop contains \\(n\\), \\(n - 1\\), \\(\\dots\\), \\(3\\), \\(2\\) inner loop iterations, summing to \\(\\frac{(n - 1)(n + 2)}{2}\\).
  • Space complexity of \\(O(1)\\), in-place sorting: Pointers \\(i\\) and \\(j\\) use a constant amount of extra space.
  • Non-stable sorting: As shown in Figure 11-3, element nums[i] may be swapped to the right of an element equal to it, causing a change in their relative order.

Figure 11-3   Selection sort non-stability example

","path":["Chapter 11. Sorting","11.2   Selection Sort"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/","level":1,"title":"11.1   Sorting Algorithm","text":"

Sorting algorithm (sorting algorithm) is used to arrange a group of data in a specific order. Sorting algorithms have extensive applications because ordered data can usually be searched, analyzed, and processed more efficiently.

As shown in Figure 11-1, data types in sorting algorithms can be integers, floating-point numbers, characters, or strings, etc. The sorting criterion can be set according to requirements, such as numerical size, character ASCII code order, or custom rules.

Figure 11-1   Data type and criterion examples

","path":["Chapter 11. Sorting","11.1   Sorting Algorithm"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/#1111-evaluation-dimensions","level":2,"title":"11.1.1   Evaluation Dimensions","text":"

Execution efficiency: We expect the time complexity of sorting algorithms to be as low as possible, with a smaller total number of operations (reducing the constant factor in time complexity). For large data volumes, execution efficiency is particularly important.

In-place property: As the name implies, in-place sorting achieves sorting by operating directly on the original array without requiring additional auxiliary arrays, thus saving memory. Typically, in-place sorting involves fewer data movement operations and runs faster.

Stability: Stable sorting ensures that the relative order of equal elements in the array does not change after sorting is completed.

Stable sorting is a necessary condition for multi-level sorting scenarios. Suppose we have a table storing student information, where column 1 and column 2 are name and age, respectively. In this case, unstable sorting may cause the ordered nature of the input data to be lost:

# Input Data Is Sorted by Name\n# (name, age)\n  ('A', 19)\n  ('B', 18)\n  ('C', 21)\n  ('D', 19)\n  ('E', 23)\n\n# Assuming We Use an Unstable Sorting Algorithm to Sort the List by Age,\n# In the Result, the Relative Positions of ('D', 19) and ('A', 19) Are Changed,\n# And the Property That the Input Data Is Sorted by Name Is Lost\n  ('B', 18)\n  ('D', 19)\n  ('A', 19)\n  ('C', 21)\n  ('E', 23)\n

Adaptability: Adaptive sorting can utilize the existing order information in the input data to reduce the amount of computation, achieving better time efficiency. The best-case time complexity of adaptive sorting algorithms is typically better than the average time complexity.

Comparison-based or not: Comparison-based sorting relies on comparison operators (\\(<\\), \\(=\\), \\(>\\)) to determine the relative order of elements, thereby sorting the entire array, with a theoretical optimal time complexity of \\(O(n \\log n)\\). Non-comparison sorting does not use comparison operators and can achieve a time complexity of \\(O(n)\\), but its versatility is relatively limited.

","path":["Chapter 11. Sorting","11.1   Sorting Algorithm"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/#1112-ideal-sorting-algorithm","level":2,"title":"11.1.2   Ideal Sorting Algorithm","text":"

Fast execution, in-place, stable, adaptive, good versatility. Clearly, no sorting algorithm has been discovered to date that combines all of these characteristics. Therefore, when selecting a sorting algorithm, it is necessary to decide based on the specific characteristics of the data and the requirements of the problem.

Next, we will learn about various sorting algorithms together and analyze the advantages and disadvantages of each sorting algorithm based on the above evaluation dimensions.

","path":["Chapter 11. Sorting","11.1   Sorting Algorithm"],"tags":[]},{"location":"chapter_sorting/summary/","level":1,"title":"11.11   Summary","text":"","path":["Chapter 11. Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_sorting/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • Bubble sort achieves sorting by swapping adjacent elements. By adding a flag to enable early return, we can optimize the best-case time complexity of bubble sort to \\(O(n)\\).
  • Insertion sort completes sorting by inserting elements from the unsorted interval into the correct position in the sorted interval each round. Although the time complexity of insertion sort is \\(O(n^2)\\), it is very popular in small data volume sorting tasks because it involves relatively few unit operations.
  • Quick sort is implemented based on sentinel partitioning operations. In sentinel partitioning, it is possible to select the worst pivot every time, causing the time complexity to degrade to \\(O(n^2)\\). Introducing median pivot or random pivot can reduce the probability of such degradation. By preferentially recursing on the shorter sub-interval, the recursion depth can be effectively reduced, optimizing the space complexity to \\(O(\\log n)\\).
  • Merge sort includes two phases: divide and merge, which typically embody the divide-and-conquer strategy. In merge sort, sorting an array requires creating auxiliary arrays, with a space complexity of \\(O(n)\\); however, the space complexity of sorting a linked list can be optimized to \\(O(1)\\).
  • Bucket sort consists of three steps: distributing data into buckets, sorting within buckets, and merging results. It also embodies the divide-and-conquer strategy and is suitable for very large data volumes. The key to bucket sort is distributing data evenly.
  • Counting sort is a special case of bucket sort, which achieves sorting by counting the number of occurrences of data. Counting sort is suitable for situations where the data volume is large but the data range is limited, and requires that data can be converted to positive integers.
  • Radix sort achieves data sorting by sorting digit by digit, requiring that data can be represented as fixed-digit numbers.
  • Overall, we hope to find a sorting algorithm that is efficient, stable, in-place, and adaptive, with good versatility. However, just like other data structures and algorithms, no sorting algorithm has been found so far that simultaneously possesses all these characteristics. In practical applications, we need to select the appropriate sorting algorithm based on the specific characteristics of the data.
  • Figure 11-19 compares mainstream sorting algorithms in terms of efficiency, stability, in-place property, and adaptability.

Figure 11-19   Sorting algorithm comparison

","path":["Chapter 11. Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_sorting/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: In what situations is the stability of sorting algorithms necessary?

In reality, we may sort based on a certain attribute of objects. For example, students have two attributes: name and height. We want to implement multi-level sorting: first sort by name to get (A, 180) (B, 185) (C, 170) (D, 170); then sort by height. Because the sorting algorithm is unstable, we may get (D, 170) (C, 170) (A, 180) (B, 185).

It can be seen that the positions of students D and C have been swapped, and the orderliness of names has been disrupted, which is something we don't want to see.

Q: Can the order of \"searching from right to left\" and \"searching from left to right\" in sentinel partitioning be swapped?

No. When we use the leftmost element as the pivot, we must first \"search from right to left\" and then \"search from left to right\". This conclusion is somewhat counterintuitive; let's analyze the reason.

The last step of sentinel partitioning partition() is to swap nums[left] and nums[i]. After the swap is complete, the elements to the left of the pivot are all <= the pivot, which requires that nums[left] >= nums[i] must hold before the last swap. Suppose we first \"search from left to right\", then if we cannot find an element larger than the pivot, we will exit the loop when i == j, at which point it may be that nums[j] == nums[i] > nums[left]. In other words, the last swap operation will swap an element larger than the pivot to the leftmost end of the array, causing sentinel partitioning to fail.

For example, given the array [0, 0, 0, 0, 1], if we first \"search from left to right\", the array after sentinel partitioning is [1, 0, 0, 0, 0], which is incorrect.

Thinking deeper, if we select nums[right] as the pivot, then it's exactly the opposite - we must first \"search from left to right\".

Q: Regarding the optimization of recursion depth in quick sort, why can selecting the shorter array ensure that the recursion depth does not exceed \\(\\log n\\)?

The recursion depth is the number of currently unreturned recursive methods. Each round of sentinel partitioning divides the original array into two sub-arrays. After recursion depth optimization, the length of the sub-array to be recursively processed is at most half of the original array length. Assuming the worst case is always half the length, the final recursion depth will be \\(\\log n\\).

Reviewing the original quick sort, we may continuously recurse on the longer array. In the worst case, it would be \\(n\\), \\(n - 1\\), \\(\\dots\\), \\(2\\), \\(1\\), with a recursion depth of \\(n\\). Recursion depth optimization can avoid this situation.

Q: When all elements in the array are equal, is the time complexity of quick sort \\(O(n^2)\\)? How should this degenerate case be handled?

Yes. For this situation, consider partitioning the array into three parts through sentinel partitioning: less than, equal to, and greater than the pivot. Only recursively process the less than and greater than parts. Under this method, an array where all input elements are equal can complete sorting in just one round of sentinel partitioning.

Q: Why is the worst-case time complexity of bucket sort \\(O(n^2)\\)?

In the worst case, all elements are distributed into the same bucket. If we use an \\(O(n^2)\\) algorithm to sort these elements, the time complexity will be \\(O(n^2)\\).

","path":["Chapter 11. Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/","level":1,"title":"Chapter 5.   Stack and Queue","text":"

Abstract

Stacks are like stacking cats, while queues are like cats lining up.

They represent LIFO (Last In First Out) and FIFO (First In First Out) logic, respectively.

","path":["Chapter 5. Stack and Queue","Chapter 5.   Stack and Queue"],"tags":[]},{"location":"chapter_stack_and_queue/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 5.1   Stack
  • 5.2   Queue
  • 5.3   Double-Ended Queue
  • 5.4   Summary
","path":["Chapter 5. Stack and Queue","Chapter 5.   Stack and Queue"],"tags":[]},{"location":"chapter_stack_and_queue/deque/","level":1,"title":"5.3   Deque","text":"

In a queue, we can only remove elements from the front or add elements at the rear. As shown in Figure 5-7, a double-ended queue (deque) provides greater flexibility, allowing the addition or removal of elements at both the front and rear.

Figure 5-7   Operations of deque

","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#531-common-deque-operations","level":2,"title":"5.3.1   Common Deque Operations","text":"

The common operations on a deque are shown in Table 5-3. The specific method names depend on the programming language used.

Table 5-3   Efficiency of Deque Operations

Method Description Time Complexity push_first() Add element to front \\(O(1)\\) push_last() Add element to rear \\(O(1)\\) pop_first() Remove front element \\(O(1)\\) pop_last() Remove rear element \\(O(1)\\) peek_first() Access front element \\(O(1)\\) peek_last() Access rear element \\(O(1)\\)

Similarly, we can directly use the deque classes already implemented in programming languages:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby deque.py
from collections import deque\n\n# Initialize deque\ndeq: deque[int] = deque()\n\n# Enqueue elements\ndeq.append(2)      # Add to rear\ndeq.append(5)\ndeq.append(4)\ndeq.appendleft(3)  # Add to front\ndeq.appendleft(1)\n\n# Access elements\nfront: int = deq[0]  # Front element\nrear: int = deq[-1]  # Rear element\n\n# Dequeue elements\npop_front: int = deq.popleft()  # Front element dequeue\npop_rear: int = deq.pop()       # Rear element dequeue\n\n# Get deque length\nsize: int = len(deq)\n\n# Check if deque is empty\nis_empty: bool = len(deq) == 0\n
deque.cpp
/* Initialize deque */\ndeque<int> deque;\n\n/* Enqueue elements */\ndeque.push_back(2);   // Add to rear\ndeque.push_back(5);\ndeque.push_back(4);\ndeque.push_front(3);  // Add to front\ndeque.push_front(1);\n\n/* Access elements */\nint front = deque.front(); // Front element\nint back = deque.back();   // Rear element\n\n/* Dequeue elements */\ndeque.pop_front();  // Front element dequeue\ndeque.pop_back();   // Rear element dequeue\n\n/* Get deque length */\nint size = deque.size();\n\n/* Check if deque is empty */\nbool empty = deque.empty();\n
deque.java
/* Initialize deque */\nDeque<Integer> deque = new LinkedList<>();\n\n/* Enqueue elements */\ndeque.offerLast(2);   // Add to rear\ndeque.offerLast(5);\ndeque.offerLast(4);\ndeque.offerFirst(3);  // Add to front\ndeque.offerFirst(1);\n\n/* Access elements */\nint peekFirst = deque.peekFirst();  // Front element\nint peekLast = deque.peekLast();    // Rear element\n\n/* Dequeue elements */\nint popFirst = deque.pollFirst();  // Front element dequeue\nint popLast = deque.pollLast();    // Rear element dequeue\n\n/* Get deque length */\nint size = deque.size();\n\n/* Check if deque is empty */\nboolean isEmpty = deque.isEmpty();\n
deque.cs
/* Initialize deque */\n// In C#, use LinkedList as a deque\nLinkedList<int> deque = new();\n\n/* Enqueue elements */\ndeque.AddLast(2);   // Add to rear\ndeque.AddLast(5);\ndeque.AddLast(4);\ndeque.AddFirst(3);  // Add to front\ndeque.AddFirst(1);\n\n/* Access elements */\nint peekFirst = deque.First.Value;  // Front element\nint peekLast = deque.Last.Value;    // Rear element\n\n/* Dequeue elements */\ndeque.RemoveFirst();  // Front element dequeue\ndeque.RemoveLast();   // Rear element dequeue\n\n/* Get deque length */\nint size = deque.Count;\n\n/* Check if deque is empty */\nbool isEmpty = deque.Count == 0;\n
deque_test.go
/* Initialize deque */\n// In Go, use list as a deque\ndeque := list.New()\n\n/* Enqueue elements */\ndeque.PushBack(2)      // Add to rear\ndeque.PushBack(5)\ndeque.PushBack(4)\ndeque.PushFront(3)     // Add to front\ndeque.PushFront(1)\n\n/* Access elements */\nfront := deque.Front() // Front element\nrear := deque.Back()   // Rear element\n\n/* Dequeue elements */\ndeque.Remove(front)    // Front element dequeue\ndeque.Remove(rear)     // Rear element dequeue\n\n/* Get deque length */\nsize := deque.Len()\n\n/* Check if deque is empty */\nisEmpty := deque.Len() == 0\n
deque.swift
/* Initialize deque */\n// Swift does not have a built-in deque class, can use Array as a deque\nvar deque: [Int] = []\n\n/* Enqueue elements */\ndeque.append(2) // Add to rear\ndeque.append(5)\ndeque.append(4)\ndeque.insert(3, at: 0) // Add to front\ndeque.insert(1, at: 0)\n\n/* Access elements */\nlet peekFirst = deque.first! // Front element\nlet peekLast = deque.last! // Rear element\n\n/* Dequeue elements */\n// When using Array simulation, popFirst has O(n) complexity\nlet popFirst = deque.removeFirst() // Front element dequeue\nlet popLast = deque.removeLast() // Rear element dequeue\n\n/* Get deque length */\nlet size = deque.count\n\n/* Check if deque is empty */\nlet isEmpty = deque.isEmpty\n
deque.js
/* Initialize deque */\n// JavaScript does not have a built-in deque, can only use Array as a deque\nconst deque = [];\n\n/* Enqueue elements */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// Please note that since it's an array, unshift() has O(n) time complexity\ndeque.unshift(3);\ndeque.unshift(1);\n\n/* Access elements */\nconst peekFirst = deque[0];\nconst peekLast = deque[deque.length - 1];\n\n/* Dequeue elements */\n// Please note that since it's an array, shift() has O(n) time complexity\nconst popFront = deque.shift();\nconst popBack = deque.pop();\n\n/* Get deque length */\nconst size = deque.length;\n\n/* Check if deque is empty */\nconst isEmpty = size === 0;\n
deque.ts
/* Initialize deque */\n// TypeScript does not have a built-in deque, can only use Array as a deque\nconst deque: number[] = [];\n\n/* Enqueue elements */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// Please note that since it's an array, unshift() has O(n) time complexity\ndeque.unshift(3);\ndeque.unshift(1);\n\n/* Access elements */\nconst peekFirst: number = deque[0];\nconst peekLast: number = deque[deque.length - 1];\n\n/* Dequeue elements */\n// Please note that since it's an array, shift() has O(n) time complexity\nconst popFront: number = deque.shift() as number;\nconst popBack: number = deque.pop() as number;\n\n/* Get deque length */\nconst size: number = deque.length;\n\n/* Check if deque is empty */\nconst isEmpty: boolean = size === 0;\n
deque.dart
/* Initialize deque */\n// In Dart, Queue is defined as a deque\nQueue<int> deque = Queue<int>();\n\n/* Enqueue elements */\ndeque.addLast(2);  // Add to rear\ndeque.addLast(5);\ndeque.addLast(4);\ndeque.addFirst(3); // Add to front\ndeque.addFirst(1);\n\n/* Access elements */\nint peekFirst = deque.first; // Front element\nint peekLast = deque.last;   // Rear element\n\n/* Dequeue elements */\nint popFirst = deque.removeFirst(); // Front element dequeue\nint popLast = deque.removeLast();   // Rear element dequeue\n\n/* Get deque length */\nint size = deque.length;\n\n/* Check if deque is empty */\nbool isEmpty = deque.isEmpty;\n
deque.rs
/* Initialize deque */\nlet mut deque: VecDeque<u32> = VecDeque::new();\n\n/* Enqueue elements */\ndeque.push_back(2);  // Add to rear\ndeque.push_back(5);\ndeque.push_back(4);\ndeque.push_front(3); // Add to front\ndeque.push_front(1);\n\n/* Access elements */\nif let Some(front) = deque.front() { // Front element\n}\nif let Some(rear) = deque.back() {   // Rear element\n}\n\n/* Dequeue elements */\nif let Some(pop_front) = deque.pop_front() { // Front element dequeue\n}\nif let Some(pop_rear) = deque.pop_back() {   // Rear element dequeue\n}\n\n/* Get deque length */\nlet size = deque.len();\n\n/* Check if deque is empty */\nlet is_empty = deque.is_empty();\n
deque.c
// C does not provide a built-in deque\n
deque.kt
/* Initialize deque */\nval deque = LinkedList<Int>()\n\n/* Enqueue elements */\ndeque.offerLast(2)  // Add to rear\ndeque.offerLast(5)\ndeque.offerLast(4)\ndeque.offerFirst(3) // Add to front\ndeque.offerFirst(1)\n\n/* Access elements */\nval peekFirst = deque.peekFirst() // Front element\nval peekLast = deque.peekLast()   // Rear element\n\n/* Dequeue elements */\nval popFirst = deque.pollFirst() // Front element dequeue\nval popLast = deque.pollLast()   // Rear element dequeue\n\n/* Get deque length */\nval size = deque.size\n\n/* Check if deque is empty */\nval isEmpty = deque.isEmpty()\n
deque.rb
# Initialize deque\n# Ruby does not have a built-in deque, can only use Array as a deque\ndeque = []\n\n# Enqueue elements\ndeque << 2\ndeque << 5\ndeque << 4\n# Please note that since it's an array, Array#unshift has O(n) time complexity\ndeque.unshift(3)\ndeque.unshift(1)\n\n# Access elements\npeek_first = deque.first\npeek_last = deque.last\n\n# Dequeue elements\n# Please note that since it's an array, Array#shift has O(n) time complexity\npop_front = deque.shift\npop_back = deque.pop\n\n# Get deque length\nsize = deque.length\n\n# Check if deque is empty\nis_empty = size.zero?\n
Code Visualization

Full Screen >

","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#532-deque-implementation","level":2,"title":"5.3.2   Deque Implementation *","text":"

The implementation of a deque is similar to that of a queue. You can choose either a linked list or an array as the underlying data structure.

","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#1-doubly-linked-list-implementation","level":3,"title":"1.   Doubly Linked List Implementation","text":"

Reviewing the previous section, we used a regular singly linked list to implement a queue because it conveniently allows deleting the head node (corresponding to dequeue) and adding new nodes after the tail node (corresponding to enqueue).

For a deque, both the front and rear can perform enqueue and dequeue operations. In other words, a deque needs to implement operations in the opposite direction as well. For this reason, we use a \"doubly linked list\" as the underlying data structure for the deque.

As shown in Figure 5-8, we treat the head and tail nodes of the doubly linked list as the front and rear of the deque, implementing functionality to add and remove nodes at both ends.

LinkedListDequepush_last()push_first()pop_last()pop_first()

Figure 5-8   Enqueue and dequeue operations in linked list implementation of deque

The implementation code is shown below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_deque.py
class ListNode:\n    \"\"\"Doubly linked list node\"\"\"\n\n    def __init__(self, val: int):\n        \"\"\"Constructor\"\"\"\n        self.val: int = val\n        self.next: ListNode | None = None  # Successor node reference\n        self.prev: ListNode | None = None  # Predecessor node reference\n\nclass LinkedListDeque:\n    \"\"\"Double-ended queue based on doubly linked list implementation\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._front: ListNode | None = None  # Head node front\n        self._rear: ListNode | None = None  # Tail node rear\n        self._size: int = 0  # Length of the double-ended queue\n\n    def size(self) -> int:\n        \"\"\"Get the length of the double-ended queue\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the double-ended queue is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int, is_front: bool):\n        \"\"\"Enqueue operation\"\"\"\n        node = ListNode(num)\n        # If the linked list is empty, make both front and rear point to node\n        if self.is_empty():\n            self._front = self._rear = node\n        # Front of the queue enqueue operation\n        elif is_front:\n            # Add node to the head of the linked list\n            self._front.prev = node\n            node.next = self._front\n            self._front = node  # Update head node\n        # Rear of the queue enqueue operation\n        else:\n            # Add node to the tail of the linked list\n            self._rear.next = node\n            node.prev = self._rear\n            self._rear = node  # Update tail node\n        self._size += 1  # Update queue length\n\n    def push_first(self, num: int):\n        \"\"\"Front of the queue enqueue\"\"\"\n        self.push(num, True)\n\n    def push_last(self, num: int):\n        \"\"\"Rear of the queue enqueue\"\"\"\n        self.push(num, False)\n\n    def pop(self, is_front: bool) -> int:\n        \"\"\"Dequeue operation\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Double-ended queue is empty\")\n        # Front of the queue dequeue operation\n        if is_front:\n            val: int = self._front.val  # Temporarily store head node value\n            # Delete head node\n            fnext: ListNode | None = self._front.next\n            if fnext is not None:\n                fnext.prev = None\n                self._front.next = None\n            self._front = fnext  # Update head node\n        # Rear of the queue dequeue operation\n        else:\n            val: int = self._rear.val  # Temporarily store tail node value\n            # Delete tail node\n            rprev: ListNode | None = self._rear.prev\n            if rprev is not None:\n                rprev.next = None\n                self._rear.prev = None\n            self._rear = rprev  # Update tail node\n        self._size -= 1  # Update queue length\n        return val\n\n    def pop_first(self) -> int:\n        \"\"\"Front of the queue dequeue\"\"\"\n        return self.pop(True)\n\n    def pop_last(self) -> int:\n        \"\"\"Rear of the queue dequeue\"\"\"\n        return self.pop(False)\n\n    def peek_first(self) -> int:\n        \"\"\"Access front of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Double-ended queue is empty\")\n        return self._front.val\n\n    def peek_last(self) -> int:\n        \"\"\"Access rear of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Double-ended queue is empty\")\n        return self._rear.val\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return array for printing\"\"\"\n        node = self._front\n        res = [0] * self.size()\n        for i in range(self.size()):\n            res[i] = node.val\n            node = node.next\n        return res\n
linkedlist_deque.cpp
/* Doubly linked list node */\nstruct DoublyListNode {\n    int val;              // Node value\n    DoublyListNode *next; // Successor node pointer\n    DoublyListNode *prev; // Predecessor node pointer\n    DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {\n    }\n};\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n  private:\n    DoublyListNode *front, *rear; // Head node front, tail node rear\n    int queSize = 0;              // Length of the double-ended queue\n\n  public:\n    /* Constructor */\n    LinkedListDeque() : front(nullptr), rear(nullptr) {\n    }\n\n    /* Destructor */\n    ~LinkedListDeque() {\n        // Traverse linked list to delete nodes and free memory\n        DoublyListNode *pre, *cur = front;\n        while (cur != nullptr) {\n            pre = cur;\n            cur = cur->next;\n            delete pre;\n        }\n    }\n\n    /* Get the length of the double-ended queue */\n    int size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue operation */\n    void push(int num, bool isFront) {\n        DoublyListNode *node = new DoublyListNode(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty())\n            front = rear = node;\n        // Front of the queue enqueue operation\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front->prev = node;\n            node->next = front;\n            front = node; // Update head node\n        // Rear of the queue enqueue operation\n        } else {\n            // Add node to the tail of the linked list\n            rear->next = node;\n            node->prev = rear;\n            rear = node; // Update tail node\n        }\n        queSize++; // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    void pushFirst(int num) {\n        push(num, true);\n    }\n\n    /* Rear of the queue enqueue */\n    void pushLast(int num) {\n        push(num, false);\n    }\n\n    /* Dequeue operation */\n    int pop(bool isFront) {\n        if (isEmpty())\n            throw out_of_range(\"Queue is empty\");\n        int val;\n        // Temporarily store head node value\n        if (isFront) {\n            val = front->val; // Delete head node\n            // Delete head node\n            DoublyListNode *fNext = front->next;\n            if (fNext != nullptr) {\n                fNext->prev = nullptr;\n                front->next = nullptr;\n            }\n            delete front;\n            front = fNext; // Update head node\n        // Temporarily store tail node value\n        } else {\n            val = rear->val; // Delete tail node\n            // Update tail node\n            DoublyListNode *rPrev = rear->prev;\n            if (rPrev != nullptr) {\n                rPrev->next = nullptr;\n                rear->prev = nullptr;\n            }\n            delete rear;\n            rear = rPrev; // Update tail node\n        }\n        queSize--; // Update queue length\n        return val;\n    }\n\n    /* Rear of the queue dequeue */\n    int popFirst() {\n        return pop(true);\n    }\n\n    /* Access rear of the queue element */\n    int popLast() {\n        return pop(false);\n    }\n\n    /* Return list for printing */\n    int peekFirst() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return front->val;\n    }\n\n    /* Driver Code */\n    int peekLast() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return rear->val;\n    }\n\n    /* Return array for printing */\n    vector<int> toVector() {\n        DoublyListNode *node = front;\n        vector<int> res(size());\n        for (int i = 0; i < res.size(); i++) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
linkedlist_deque.java
/* Doubly linked list node */\nclass ListNode {\n    int val; // Node value\n    ListNode next; // Successor node reference\n    ListNode prev; // Predecessor node reference\n\n    ListNode(int val) {\n        this.val = val;\n        prev = next = null;\n    }\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    private ListNode front, rear; // Head node front, tail node rear\n    private int queSize = 0; // Length of the double-ended queue\n\n    public LinkedListDeque() {\n        front = rear = null;\n    }\n\n    /* Get the length of the double-ended queue */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue operation */\n    private void push(int num, boolean isFront) {\n        ListNode node = new ListNode(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty())\n            front = rear = node;\n        // Front of the queue enqueue operation\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front.prev = node;\n            node.next = front;\n            front = node; // Update head node\n        // Rear of the queue enqueue operation\n        } else {\n            // Add node to the tail of the linked list\n            rear.next = node;\n            node.prev = rear;\n            rear = node; // Update tail node\n        }\n        queSize++; // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    public void pushFirst(int num) {\n        push(num, true);\n    }\n\n    /* Rear of the queue enqueue */\n    public void pushLast(int num) {\n        push(num, false);\n    }\n\n    /* Dequeue operation */\n    private int pop(boolean isFront) {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        int val;\n        // Temporarily store head node value\n        if (isFront) {\n            val = front.val; // Delete head node\n            // Delete head node\n            ListNode fNext = front.next;\n            if (fNext != null) {\n                fNext.prev = null;\n                front.next = null;\n            }\n            front = fNext; // Update head node\n        // Temporarily store tail node value\n        } else {\n            val = rear.val; // Delete tail node\n            // Update tail node\n            ListNode rPrev = rear.prev;\n            if (rPrev != null) {\n                rPrev.next = null;\n                rear.prev = null;\n            }\n            rear = rPrev; // Update tail node\n        }\n        queSize--; // Update queue length\n        return val;\n    }\n\n    /* Rear of the queue dequeue */\n    public int popFirst() {\n        return pop(true);\n    }\n\n    /* Access rear of the queue element */\n    public int popLast() {\n        return pop(false);\n    }\n\n    /* Return list for printing */\n    public int peekFirst() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return front.val;\n    }\n\n    /* Driver Code */\n    public int peekLast() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return rear.val;\n    }\n\n    /* Return array for printing */\n    public int[] toArray() {\n        ListNode node = front;\n        int[] res = new int[size()];\n        for (int i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_deque.cs
/* Doubly linked list node */\nclass ListNode(int val) {\n    public int val = val;       // Node value\n    public ListNode? next = null; // Successor node reference\n    public ListNode? prev = null; // Predecessor node reference\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    ListNode? front, rear; // Head node front, tail node rear\n    int queSize = 0;      // Length of the double-ended queue\n\n    public LinkedListDeque() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the length of the double-ended queue */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Enqueue operation */\n    void Push(int num, bool isFront) {\n        ListNode node = new(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (IsEmpty()) {\n            front = node;\n            rear = node;\n        }\n        // Front of the queue enqueue operation\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front!.prev = node;\n            node.next = front;\n            front = node; // Update head node\n        }\n        // Rear of the queue enqueue operation\n        else {\n            // Add node to the tail of the linked list\n            rear!.next = node;\n            node.prev = rear;\n            rear = node;  // Update tail node\n        }\n\n        queSize++; // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    public void PushFirst(int num) {\n        Push(num, true);\n    }\n\n    /* Rear of the queue enqueue */\n    public void PushLast(int num) {\n        Push(num, false);\n    }\n\n    /* Dequeue operation */\n    int? Pop(bool isFront) {\n        if (IsEmpty())\n            throw new Exception();\n        int? val;\n        // Temporarily store head node value\n        if (isFront) {\n            val = front?.val; // Delete head node\n            // Delete head node\n            ListNode? fNext = front?.next;\n            if (fNext != null) {\n                fNext.prev = null;\n                front!.next = null;\n            }\n            front = fNext;   // Update head node\n        }\n        // Temporarily store tail node value\n        else {\n            val = rear?.val;  // Delete tail node\n            // Update tail node\n            ListNode? rPrev = rear?.prev;\n            if (rPrev != null) {\n                rPrev.next = null;\n                rear!.prev = null;\n            }\n            rear = rPrev;    // Update tail node\n        }\n\n        queSize--; // Update queue length\n        return val;\n    }\n\n    /* Rear of the queue dequeue */\n    public int? PopFirst() {\n        return Pop(true);\n    }\n\n    /* Access rear of the queue element */\n    public int? PopLast() {\n        return Pop(false);\n    }\n\n    /* Return list for printing */\n    public int? PeekFirst() {\n        if (IsEmpty())\n            throw new Exception();\n        return front?.val;\n    }\n\n    /* Driver Code */\n    public int? PeekLast() {\n        if (IsEmpty())\n            throw new Exception();\n        return rear?.val;\n    }\n\n    /* Return array for printing */\n    public int?[] ToArray() {\n        ListNode? node = front;\n        int?[] res = new int?[Size()];\n        for (int i = 0; i < res.Length; i++) {\n            res[i] = node?.val;\n            node = node?.next;\n        }\n\n        return res;\n    }\n}\n
linkedlist_deque.go
/* Double-ended queue based on doubly linked list implementation */\ntype linkedListDeque struct {\n    // Use built-in package list\n    data *list.List\n}\n\n/* Initialize deque */\nfunc newLinkedListDeque() *linkedListDeque {\n    return &linkedListDeque{\n        data: list.New(),\n    }\n}\n\n/* Front element enqueue */\nfunc (s *linkedListDeque) pushFirst(value any) {\n    s.data.PushFront(value)\n}\n\n/* Rear element enqueue */\nfunc (s *linkedListDeque) pushLast(value any) {\n    s.data.PushBack(value)\n}\n\n/* Check if the double-ended queue is empty */\nfunc (s *linkedListDeque) popFirst() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Rear element dequeue */\nfunc (s *linkedListDeque) popLast() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Return list for printing */\nfunc (s *linkedListDeque) peekFirst() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    return e.Value\n}\n\n/* Driver Code */\nfunc (s *linkedListDeque) peekLast() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    return e.Value\n}\n\n/* Get the length of the queue */\nfunc (s *linkedListDeque) size() int {\n    return s.data.Len()\n}\n\n/* Check if the queue is empty */\nfunc (s *linkedListDeque) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get List for printing */\nfunc (s *linkedListDeque) toList() *list.List {\n    return s.data\n}\n
linkedlist_deque.swift
/* Doubly linked list node */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Successor node reference\n    weak var prev: ListNode? // Predecessor node reference\n\n    init(val: Int) {\n        self.val = val\n    }\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    private var front: ListNode? // Head node front\n    private var rear: ListNode? // Tail node rear\n    private var _size: Int // Length of the double-ended queue\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the length of the double-ended queue */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check if the double-ended queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue operation */\n    private func push(num: Int, isFront: Bool) {\n        let node = ListNode(val: num)\n        // If the linked list is empty, make both front and rear point to node\n        if isEmpty() {\n            front = node\n            rear = node\n        }\n        // Front of the queue enqueue operation\n        else if isFront {\n            // Add node to the head of the linked list\n            front?.prev = node\n            node.next = front\n            front = node // Update head node\n        }\n        // Rear of the queue enqueue operation\n        else {\n            // Add node to the tail of the linked list\n            rear?.next = node\n            node.prev = rear\n            rear = node // Update tail node\n        }\n        _size += 1 // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    func pushFirst(num: Int) {\n        push(num: num, isFront: true)\n    }\n\n    /* Rear of the queue enqueue */\n    func pushLast(num: Int) {\n        push(num: num, isFront: false)\n    }\n\n    /* Dequeue operation */\n    private func pop(isFront: Bool) -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        let val: Int\n        // Temporarily store head node value\n        if isFront {\n            val = front!.val // Delete head node\n            // Delete head node\n            let fNext = front?.next\n            if fNext != nil {\n                fNext?.prev = nil\n                front?.next = nil\n            }\n            front = fNext // Update head node\n        }\n        // Temporarily store tail node value\n        else {\n            val = rear!.val // Delete tail node\n            // Update tail node\n            let rPrev = rear?.prev\n            if rPrev != nil {\n                rPrev?.next = nil\n                rear?.prev = nil\n            }\n            rear = rPrev // Update tail node\n        }\n        _size -= 1 // Update queue length\n        return val\n    }\n\n    /* Rear of the queue dequeue */\n    func popFirst() -> Int {\n        pop(isFront: true)\n    }\n\n    /* Access rear of the queue element */\n    func popLast() -> Int {\n        pop(isFront: false)\n    }\n\n    /* Return list for printing */\n    func peekFirst() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return front!.val\n    }\n\n    /* Driver Code */\n    func peekLast() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return rear!.val\n    }\n\n    /* Return array for printing */\n    func toArray() -> [Int] {\n        var node = front\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
linkedlist_deque.js
/* Doubly linked list node */\nclass ListNode {\n    prev; // Predecessor node reference (pointer)\n    next; // Successor node reference (pointer)\n    val; // Node value\n\n    constructor(val) {\n        this.val = val;\n        this.next = null;\n        this.prev = null;\n    }\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    #front; // Head node front\n    #rear; // Tail node rear\n    #queSize; // Length of the double-ended queue\n\n    constructor() {\n        this.#front = null;\n        this.#rear = null;\n        this.#queSize = 0;\n    }\n\n    /* Rear of the queue enqueue operation */\n    pushLast(val) {\n        const node = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.#queSize === 0) {\n            this.#front = node;\n            this.#rear = node;\n        } else {\n            // Add node to the tail of the linked list\n            this.#rear.next = node;\n            node.prev = this.#rear;\n            this.#rear = node; // Update tail node\n        }\n        this.#queSize++;\n    }\n\n    /* Front of the queue enqueue operation */\n    pushFirst(val) {\n        const node = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.#queSize === 0) {\n            this.#front = node;\n            this.#rear = node;\n        } else {\n            // Add node to the head of the linked list\n            this.#front.prev = node;\n            node.next = this.#front;\n            this.#front = node; // Update head node\n        }\n        this.#queSize++;\n    }\n\n    /* Temporarily store tail node value */\n    popLast() {\n        if (this.#queSize === 0) {\n            return null;\n        }\n        const value = this.#rear.val; // Store tail node value\n        // Update tail node\n        let temp = this.#rear.prev;\n        if (temp !== null) {\n            temp.next = null;\n            this.#rear.prev = null;\n        }\n        this.#rear = temp; // Update tail node\n        this.#queSize--;\n        return value;\n    }\n\n    /* Temporarily store head node value */\n    popFirst() {\n        if (this.#queSize === 0) {\n            return null;\n        }\n        const value = this.#front.val; // Store tail node value\n        // Delete head node\n        let temp = this.#front.next;\n        if (temp !== null) {\n            temp.prev = null;\n            this.#front.next = null;\n        }\n        this.#front = temp; // Update head node\n        this.#queSize--;\n        return value;\n    }\n\n    /* Driver Code */\n    peekLast() {\n        return this.#queSize === 0 ? null : this.#rear.val;\n    }\n\n    /* Return list for printing */\n    peekFirst() {\n        return this.#queSize === 0 ? null : this.#front.val;\n    }\n\n    /* Get the length of the double-ended queue */\n    size() {\n        return this.#queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Print deque */\n    print() {\n        const arr = [];\n        let temp = this.#front;\n        while (temp !== null) {\n            arr.push(temp.val);\n            temp = temp.next;\n        }\n        console.log('[' + arr.join(', ') + ']');\n    }\n}\n
linkedlist_deque.ts
/* Doubly linked list node */\nclass ListNode {\n    prev: ListNode; // Predecessor node reference (pointer)\n    next: ListNode; // Successor node reference (pointer)\n    val: number; // Node value\n\n    constructor(val: number) {\n        this.val = val;\n        this.next = null;\n        this.prev = null;\n    }\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    private front: ListNode; // Head node front\n    private rear: ListNode; // Tail node rear\n    private queSize: number; // Length of the double-ended queue\n\n    constructor() {\n        this.front = null;\n        this.rear = null;\n        this.queSize = 0;\n    }\n\n    /* Rear of the queue enqueue operation */\n    pushLast(val: number): void {\n        const node: ListNode = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.queSize === 0) {\n            this.front = node;\n            this.rear = node;\n        } else {\n            // Add node to the tail of the linked list\n            this.rear.next = node;\n            node.prev = this.rear;\n            this.rear = node; // Update tail node\n        }\n        this.queSize++;\n    }\n\n    /* Front of the queue enqueue operation */\n    pushFirst(val: number): void {\n        const node: ListNode = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.queSize === 0) {\n            this.front = node;\n            this.rear = node;\n        } else {\n            // Add node to the head of the linked list\n            this.front.prev = node;\n            node.next = this.front;\n            this.front = node; // Update head node\n        }\n        this.queSize++;\n    }\n\n    /* Temporarily store tail node value */\n    popLast(): number {\n        if (this.queSize === 0) {\n            return null;\n        }\n        const value: number = this.rear.val; // Store tail node value\n        // Update tail node\n        let temp: ListNode = this.rear.prev;\n        if (temp !== null) {\n            temp.next = null;\n            this.rear.prev = null;\n        }\n        this.rear = temp; // Update tail node\n        this.queSize--;\n        return value;\n    }\n\n    /* Temporarily store head node value */\n    popFirst(): number {\n        if (this.queSize === 0) {\n            return null;\n        }\n        const value: number = this.front.val; // Store tail node value\n        // Delete head node\n        let temp: ListNode = this.front.next;\n        if (temp !== null) {\n            temp.prev = null;\n            this.front.next = null;\n        }\n        this.front = temp; // Update head node\n        this.queSize--;\n        return value;\n    }\n\n    /* Driver Code */\n    peekLast(): number {\n        return this.queSize === 0 ? null : this.rear.val;\n    }\n\n    /* Return list for printing */\n    peekFirst(): number {\n        return this.queSize === 0 ? null : this.front.val;\n    }\n\n    /* Get the length of the double-ended queue */\n    size(): number {\n        return this.queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Print deque */\n    print(): void {\n        const arr: number[] = [];\n        let temp: ListNode = this.front;\n        while (temp !== null) {\n            arr.push(temp.val);\n            temp = temp.next;\n        }\n        console.log('[' + arr.join(', ') + ']');\n    }\n}\n
linkedlist_deque.dart
/* Doubly linked list node */\nclass ListNode {\n  int val; // Node value\n  ListNode? next; // Successor node reference\n  ListNode? prev; // Predecessor node reference\n\n  ListNode(this.val, {this.next, this.prev});\n}\n\n/* Deque implemented based on doubly linked list */\nclass LinkedListDeque {\n  late ListNode? _front; // Head node _front\n  late ListNode? _rear; // Tail node _rear\n  int _queSize = 0; // Length of the double-ended queue\n\n  LinkedListDeque() {\n    this._front = null;\n    this._rear = null;\n  }\n\n  /* Get deque length */\n  int size() {\n    return this._queSize;\n  }\n\n  /* Check if the double-ended queue is empty */\n  bool isEmpty() {\n    return size() == 0;\n  }\n\n  /* Enqueue operation */\n  void push(int _num, bool isFront) {\n    final ListNode node = ListNode(_num);\n    if (isEmpty()) {\n      // If list is empty, let both _front and _rear point to node\n      _front = _rear = node;\n    } else if (isFront) {\n      // Front of the queue enqueue operation\n      // Add node to the head of the linked list\n      _front!.prev = node;\n      node.next = _front;\n      _front = node; // Update head node\n    } else {\n      // Rear of the queue enqueue operation\n      // Add node to the tail of the linked list\n      _rear!.next = node;\n      node.prev = _rear;\n      _rear = node; // Update tail node\n    }\n    _queSize++; // Update queue length\n  }\n\n  /* Front of the queue enqueue */\n  void pushFirst(int _num) {\n    push(_num, true);\n  }\n\n  /* Rear of the queue enqueue */\n  void pushLast(int _num) {\n    push(_num, false);\n  }\n\n  /* Dequeue operation */\n  int? pop(bool isFront) {\n    // If queue is empty, return null directly\n    if (isEmpty()) {\n      return null;\n    }\n    final int val;\n    if (isFront) {\n      // Temporarily store head node value\n      val = _front!.val; // Delete head node\n      // Delete head node\n      ListNode? fNext = _front!.next;\n      if (fNext != null) {\n        fNext.prev = null;\n        _front!.next = null;\n      }\n      _front = fNext; // Update head node\n    } else {\n      // Temporarily store tail node value\n      val = _rear!.val; // Delete tail node\n      // Update tail node\n      ListNode? rPrev = _rear!.prev;\n      if (rPrev != null) {\n        rPrev.next = null;\n        _rear!.prev = null;\n      }\n      _rear = rPrev; // Update tail node\n    }\n    _queSize--; // Update queue length\n    return val;\n  }\n\n  /* Rear of the queue dequeue */\n  int? popFirst() {\n    return pop(true);\n  }\n\n  /* Access rear of the queue element */\n  int? popLast() {\n    return pop(false);\n  }\n\n  /* Return list for printing */\n  int? peekFirst() {\n    return _front?.val;\n  }\n\n  /* Driver Code */\n  int? peekLast() {\n    return _rear?.val;\n  }\n\n  /* Return array for printing */\n  List<int> toArray() {\n    ListNode? node = _front;\n    final List<int> res = [];\n    for (int i = 0; i < _queSize; i++) {\n      res.add(node!.val);\n      node = node.next;\n    }\n    return res;\n  }\n}\n
linkedlist_deque.rs
/* Doubly linked list node */\npub struct ListNode<T> {\n    pub val: T,                                 // Node value\n    pub next: Option<Rc<RefCell<ListNode<T>>>>, // Successor node pointer\n    pub prev: Option<Rc<RefCell<ListNode<T>>>>, // Predecessor node pointer\n}\n\nimpl<T> ListNode<T> {\n    pub fn new(val: T) -> Rc<RefCell<ListNode<T>>> {\n        Rc::new(RefCell::new(ListNode {\n            val,\n            next: None,\n            prev: None,\n        }))\n    }\n}\n\n/* Double-ended queue based on doubly linked list implementation */\n#[allow(dead_code)]\npub struct LinkedListDeque<T> {\n    front: Option<Rc<RefCell<ListNode<T>>>>, // Head node front\n    rear: Option<Rc<RefCell<ListNode<T>>>>,  // Tail node rear\n    que_size: usize,                         // Length of the double-ended queue\n}\n\nimpl<T: Copy> LinkedListDeque<T> {\n    pub fn new() -> Self {\n        Self {\n            front: None,\n            rear: None,\n            que_size: 0,\n        }\n    }\n\n    /* Get the length of the double-ended queue */\n    pub fn size(&self) -> usize {\n        return self.que_size;\n    }\n\n    /* Check if the double-ended queue is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.que_size == 0;\n    }\n\n    /* Enqueue operation */\n    fn push(&mut self, num: T, is_front: bool) {\n        let node = ListNode::new(num);\n        // Front of the queue enqueue operation\n        if is_front {\n            match self.front.take() {\n                // If the linked list is empty, make both front and rear point to node\n                None => {\n                    self.rear = Some(node.clone());\n                    self.front = Some(node);\n                }\n                // Add node to the head of the linked list\n                Some(old_front) => {\n                    old_front.borrow_mut().prev = Some(node.clone());\n                    node.borrow_mut().next = Some(old_front);\n                    self.front = Some(node); // Update head node\n                }\n            }\n        }\n        // Rear of the queue enqueue operation\n        else {\n            match self.rear.take() {\n                // If the linked list is empty, make both front and rear point to node\n                None => {\n                    self.front = Some(node.clone());\n                    self.rear = Some(node);\n                }\n                // Add node to the tail of the linked list\n                Some(old_rear) => {\n                    old_rear.borrow_mut().next = Some(node.clone());\n                    node.borrow_mut().prev = Some(old_rear);\n                    self.rear = Some(node); // Update tail node\n                }\n            }\n        }\n        self.que_size += 1; // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    pub fn push_first(&mut self, num: T) {\n        self.push(num, true);\n    }\n\n    /* Rear of the queue enqueue */\n    pub fn push_last(&mut self, num: T) {\n        self.push(num, false);\n    }\n\n    /* Dequeue operation */\n    fn pop(&mut self, is_front: bool) -> Option<T> {\n        // If queue is empty, return None directly\n        if self.is_empty() {\n            return None;\n        };\n        // Temporarily store head node value\n        if is_front {\n            self.front.take().map(|old_front| {\n                match old_front.borrow_mut().next.take() {\n                    Some(new_front) => {\n                        new_front.borrow_mut().prev.take();\n                        self.front = Some(new_front); // Update head node\n                    }\n                    None => {\n                        self.rear.take();\n                    }\n                }\n                self.que_size -= 1; // Update queue length\n                old_front.borrow().val\n            })\n        }\n        // Temporarily store tail node value\n        else {\n            self.rear.take().map(|old_rear| {\n                match old_rear.borrow_mut().prev.take() {\n                    Some(new_rear) => {\n                        new_rear.borrow_mut().next.take();\n                        self.rear = Some(new_rear); // Update tail node\n                    }\n                    None => {\n                        self.front.take();\n                    }\n                }\n                self.que_size -= 1; // Update queue length\n                old_rear.borrow().val\n            })\n        }\n    }\n\n    /* Rear of the queue dequeue */\n    pub fn pop_first(&mut self) -> Option<T> {\n        return self.pop(true);\n    }\n\n    /* Access rear of the queue element */\n    pub fn pop_last(&mut self) -> Option<T> {\n        return self.pop(false);\n    }\n\n    /* Return list for printing */\n    pub fn peek_first(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.front.as_ref()\n    }\n\n    /* Driver Code */\n    pub fn peek_last(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.rear.as_ref()\n    }\n\n    /* Return array for printing */\n    pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n        let mut res: Vec<T> = Vec::new();\n        fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {\n            if let Some(cur) = cur {\n                res.push(cur.borrow().val);\n                recur(cur.borrow().next.as_ref(), res);\n            }\n        }\n\n        recur(head, &mut res);\n        res\n    }\n}\n
linkedlist_deque.c
/* Doubly linked list node */\ntypedef struct DoublyListNode {\n    int val;                     // Node value\n    struct DoublyListNode *next; // Successor node\n    struct DoublyListNode *prev; // Predecessor node\n} DoublyListNode;\n\n/* Constructor */\nDoublyListNode *newDoublyListNode(int num) {\n    DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode));\n    new->val = num;\n    new->next = NULL;\n    new->prev = NULL;\n    return new;\n}\n\n/* Destructor */\nvoid delDoublyListNode(DoublyListNode *node) {\n    free(node);\n}\n\n/* Double-ended queue based on doubly linked list implementation */\ntypedef struct {\n    DoublyListNode *front, *rear; // Head node front, tail node rear\n    int queSize;                  // Length of the double-ended queue\n} LinkedListDeque;\n\n/* Constructor */\nLinkedListDeque *newLinkedListDeque() {\n    LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque));\n    deque->front = NULL;\n    deque->rear = NULL;\n    deque->queSize = 0;\n    return deque;\n}\n\n/* Destructor */\nvoid delLinkedListdeque(LinkedListDeque *deque) {\n    // Free all nodes\n    for (int i = 0; i < deque->queSize && deque->front != NULL; i++) {\n        DoublyListNode *tmp = deque->front;\n        deque->front = deque->front->next;\n        free(tmp);\n    }\n    // Free deque structure\n    free(deque);\n}\n\n/* Get the length of the queue */\nint size(LinkedListDeque *deque) {\n    return deque->queSize;\n}\n\n/* Check if the queue is empty */\nbool empty(LinkedListDeque *deque) {\n    return (size(deque) == 0);\n}\n\n/* Enqueue */\nvoid push(LinkedListDeque *deque, int num, bool isFront) {\n    DoublyListNode *node = newDoublyListNode(num);\n    // If list is empty, set both front and rear to node\n    if (empty(deque)) {\n        deque->front = deque->rear = node;\n    }\n    // Front of the queue enqueue operation\n    else if (isFront) {\n        // Add node to the head of the linked list\n        deque->front->prev = node;\n        node->next = deque->front;\n        deque->front = node; // Update head node\n    }\n    // Rear of the queue enqueue operation\n    else {\n        // Add node to the tail of the linked list\n        deque->rear->next = node;\n        node->prev = deque->rear;\n        deque->rear = node;\n    }\n    deque->queSize++; // Update queue length\n}\n\n/* Front of the queue enqueue */\nvoid pushFirst(LinkedListDeque *deque, int num) {\n    push(deque, num, true);\n}\n\n/* Rear of the queue enqueue */\nvoid pushLast(LinkedListDeque *deque, int num) {\n    push(deque, num, false);\n}\n\n/* Return list for printing */\nint peekFirst(LinkedListDeque *deque) {\n    assert(size(deque) && deque->front);\n    return deque->front->val;\n}\n\n/* Driver Code */\nint peekLast(LinkedListDeque *deque) {\n    assert(size(deque) && deque->rear);\n    return deque->rear->val;\n}\n\n/* Dequeue */\nint pop(LinkedListDeque *deque, bool isFront) {\n    if (empty(deque))\n        return -1;\n    int val;\n    // Temporarily store head node value\n    if (isFront) {\n        val = peekFirst(deque); // Delete head node\n        DoublyListNode *fNext = deque->front->next;\n        if (fNext) {\n            fNext->prev = NULL;\n            deque->front->next = NULL;\n        }\n        delDoublyListNode(deque->front);\n        deque->front = fNext; // Update head node\n    }\n    // Temporarily store tail node value\n    else {\n        val = peekLast(deque); // Delete tail node\n        DoublyListNode *rPrev = deque->rear->prev;\n        if (rPrev) {\n            rPrev->next = NULL;\n            deque->rear->prev = NULL;\n        }\n        delDoublyListNode(deque->rear);\n        deque->rear = rPrev; // Update tail node\n    }\n    deque->queSize--; // Update queue length\n    return val;\n}\n\n/* Rear of the queue dequeue */\nint popFirst(LinkedListDeque *deque) {\n    return pop(deque, true);\n}\n\n/* Access rear of the queue element */\nint popLast(LinkedListDeque *deque) {\n    return pop(deque, false);\n}\n\n/* Print queue */\nvoid printLinkedListDeque(LinkedListDeque *deque) {\n    int *arr = malloc(sizeof(int) * deque->queSize);\n    // Copy data from list to array\n    int i;\n    DoublyListNode *node;\n    for (i = 0, node = deque->front; i < deque->queSize; i++) {\n        arr[i] = node->val;\n        node = node->next;\n    }\n    printArray(arr, deque->queSize);\n    free(arr);\n}\n
linkedlist_deque.kt
/* Doubly linked list node */\nclass ListNode(var _val: Int) {\n    // Node value\n    var next: ListNode? = null // Successor node reference\n    var prev: ListNode? = null // Predecessor node reference\n}\n\n/* Double-ended queue based on doubly linked list implementation */\nclass LinkedListDeque {\n    private var front: ListNode? = null // Head node front\n    private var rear: ListNode? = null // Tail node rear\n    private var queSize: Int = 0 // Length of the double-ended queue\n\n    /* Get the length of the double-ended queue */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check if the double-ended queue is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Enqueue operation */\n    fun push(num: Int, isFront: Boolean) {\n        val node = ListNode(num)\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty()) {\n            rear = node\n            front = rear\n            // Front of the queue enqueue operation\n        } else if (isFront) {\n            // Add node to the head of the linked list\n            front?.prev = node\n            node.next = front\n            front = node // Update head node\n            // Rear of the queue enqueue operation\n        } else {\n            // Add node to the tail of the linked list\n            rear?.next = node\n            node.prev = rear\n            rear = node // Update tail node\n        }\n        queSize++ // Update queue length\n    }\n\n    /* Front of the queue enqueue */\n    fun pushFirst(num: Int) {\n        push(num, true)\n    }\n\n    /* Rear of the queue enqueue */\n    fun pushLast(num: Int) {\n        push(num, false)\n    }\n\n    /* Dequeue operation */\n    fun pop(isFront: Boolean): Int {\n        if (isEmpty()) \n            throw IndexOutOfBoundsException()\n        val _val: Int\n        // Temporarily store head node value\n        if (isFront) {\n            _val = front!!._val // Delete head node\n            // Delete head node\n            val fNext = front!!.next\n            if (fNext != null) {\n                fNext.prev = null\n                front!!.next = null\n            }\n            front = fNext // Update head node\n            // Temporarily store tail node value\n        } else {\n            _val = rear!!._val // Delete tail node\n            // Update tail node\n            val rPrev = rear!!.prev\n            if (rPrev != null) {\n                rPrev.next = null\n                rear!!.prev = null\n            }\n            rear = rPrev // Update tail node\n        }\n        queSize-- // Update queue length\n        return _val\n    }\n\n    /* Rear of the queue dequeue */\n    fun popFirst(): Int {\n        return pop(true)\n    }\n\n    /* Access rear of the queue element */\n    fun popLast(): Int {\n        return pop(false)\n    }\n\n    /* Return list for printing */\n    fun peekFirst(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return front!!._val\n    }\n\n    /* Driver Code */\n    fun peekLast(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return rear!!._val\n    }\n\n    /* Return array for printing */\n    fun toArray(): IntArray {\n        var node = front\n        val res = IntArray(size())\n        for (i in res.indices) {\n            res[i] = node!!._val\n            node = node.next\n        }\n        return res\n    }\n}\n
linkedlist_deque.rb
=begin\nFile: linkedlist_deque.rb\nCreated Time: 2024-04-06\nAuthor: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)\n=end\n\n### Doubly linked list node\nclass ListNode\n  attr_accessor :val\n  attr_accessor :next # Successor node reference\n  attr_accessor :prev # Predecessor node reference\n\n  ### Constructor ###\n  def initialize(val)\n    @val = val\n  end\nend\n\n### Deque based on doubly linked list ###\nclass LinkedListDeque\n  ### Get deque length ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @front = nil  # Head node front\n    @rear = nil   # Tail node rear\n    @size = 0     # Length of the double-ended queue\n  end\n\n  ### Check if deque is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue operation ###\n  def push(num, is_front)\n    node = ListNode.new(num)\n    # If list is empty, set both front and rear to node\n    if is_empty?\n      @front = @rear = node\n    # Front of the queue enqueue operation\n    elsif is_front\n      # Add node to the head of the linked list\n      @front.prev = node\n      node.next = @front\n      @front = node # Update head node\n    # Rear of the queue enqueue operation\n    else\n      # Add node to the tail of the linked list\n      @rear.next = node\n      node.prev = @rear\n      @rear = node # Update tail node\n    end\n    @size += 1 # Update queue length\n  end\n\n  ### Enqueue at front ###\n  def push_first(num)\n    push(num, true)\n  end\n\n  ### Enqueue at rear ###\n  def push_last(num)\n    push(num, false)\n  end\n\n  ### Dequeue operation ###\n  def pop(is_front)\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    # Temporarily store head node value\n    if is_front\n      val = @front.val # Delete head node\n      # Delete head node\n      fnext = @front.next\n      unless fnext.nil?\n        fnext.prev = nil\n        @front.next = nil\n      end\n      @front = fnext # Update head node\n    # Temporarily store tail node value\n    else\n      val = @rear.val # Delete tail node\n      # Update tail node\n      rprev = @rear.prev\n      unless rprev.nil?\n        rprev.next = nil\n        @rear.prev = nil\n      end\n      @rear = rprev # Update tail node\n    end\n    @size -= 1 # Update queue length\n\n    val\n  end\n\n  ### Dequeue from front ###\n  def pop_first\n    pop(true)\n  end\n\n  ### Dequeue from front ###\n  def pop_last\n    pop(false)\n  end\n\n  ### Access front element ###\n  def peek_first\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @front.val\n  end\n\n  ### Access rear element ###\n  def peek_last\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @rear.val\n  end\n\n  ### Return array for printing ###\n  def to_array\n    node = @front\n    res = Array.new(size, 0)\n    for i in 0...size\n      res[i] = node.val\n      node = node.next\n    end\n    res\n  end\nend\n
","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#2-array-implementation","level":3,"title":"2.   Array Implementation","text":"

As shown in Figure 5-9, similar to implementing a queue based on an array, we can also use a circular array to implement a deque.

ArrayDequepush_last()push_first()pop_last()pop_first()

Figure 5-9   Enqueue and dequeue operations in array implementation of deque

Based on the queue implementation, we only need to add methods for \"enqueue at front\" and \"dequeue from rear\":

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_deque.py
class ArrayDeque:\n    \"\"\"Double-ended queue based on circular array implementation\"\"\"\n\n    def __init__(self, capacity: int):\n        \"\"\"Constructor\"\"\"\n        self._nums: list[int] = [0] * capacity\n        self._front: int = 0\n        self._size: int = 0\n\n    def capacity(self) -> int:\n        \"\"\"Get the capacity of the double-ended queue\"\"\"\n        return len(self._nums)\n\n    def size(self) -> int:\n        \"\"\"Get the length of the double-ended queue\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the double-ended queue is empty\"\"\"\n        return self._size == 0\n\n    def index(self, i: int) -> int:\n        \"\"\"Calculate circular array index\"\"\"\n        # Use modulo operation to wrap the array head and tail together\n        # When i passes the tail of the array, return to the head\n        # When i passes the head of the array, return to the tail\n        return (i + self.capacity()) % self.capacity()\n\n    def push_first(self, num: int):\n        \"\"\"Front of the queue enqueue\"\"\"\n        if self._size == self.capacity():\n            print(\"Double-ended queue is full\")\n            return\n        # Front pointer moves one position to the left\n        # Use modulo operation to wrap front around to the tail after passing the head of the array\n        self._front = self.index(self._front - 1)\n        # Add num to the front of the queue\n        self._nums[self._front] = num\n        self._size += 1\n\n    def push_last(self, num: int):\n        \"\"\"Rear of the queue enqueue\"\"\"\n        if self._size == self.capacity():\n            print(\"Double-ended queue is full\")\n            return\n        # Calculate rear pointer, points to rear index + 1\n        rear = self.index(self._front + self._size)\n        # Add num to the rear of the queue\n        self._nums[rear] = num\n        self._size += 1\n\n    def pop_first(self) -> int:\n        \"\"\"Front of the queue dequeue\"\"\"\n        num = self.peek_first()\n        # Front pointer moves one position backward\n        self._front = self.index(self._front + 1)\n        self._size -= 1\n        return num\n\n    def pop_last(self) -> int:\n        \"\"\"Rear of the queue dequeue\"\"\"\n        num = self.peek_last()\n        self._size -= 1\n        return num\n\n    def peek_first(self) -> int:\n        \"\"\"Access front of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Double-ended queue is empty\")\n        return self._nums[self._front]\n\n    def peek_last(self) -> int:\n        \"\"\"Access rear of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Double-ended queue is empty\")\n        # Calculate tail element index\n        last = self.index(self._front + self._size - 1)\n        return self._nums[last]\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return array for printing\"\"\"\n        # Only convert list elements within the valid length range\n        res = []\n        for i in range(self._size):\n            res.append(self._nums[self.index(self._front + i)])\n        return res\n
array_deque.cpp
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n  private:\n    vector<int> nums; // Array for storing double-ended queue elements\n    int front;        // Front pointer, points to the front of the queue element\n    int queSize;      // Double-ended queue length\n\n  public:\n    /* Constructor */\n    ArrayDeque(int capacity) {\n        nums.resize(capacity);\n        front = queSize = 0;\n    }\n\n    /* Get the capacity of the double-ended queue */\n    int capacity() {\n        return nums.size();\n    }\n\n    /* Get the length of the double-ended queue */\n    int size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    bool isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate circular array index */\n    int index(int i) {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + capacity()) % capacity();\n    }\n\n    /* Front of the queue enqueue */\n    void pushFirst(int num) {\n        if (queSize == capacity()) {\n            cout << \"Double-ended queue is full\" << endl;\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        front = index(front - 1);\n        // Add num to front of queue\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue enqueue */\n    void pushLast(int num) {\n        if (queSize == capacity()) {\n            cout << \"Double-ended queue is full\" << endl;\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        int rear = index(front + queSize);\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue dequeue */\n    int popFirst() {\n        int num = peekFirst();\n        // Move front pointer backward by one position\n        front = index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Access rear of the queue element */\n    int popLast() {\n        int num = peekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    int peekFirst() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return nums[front];\n    }\n\n    /* Driver Code */\n    int peekLast() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        // Initialize double-ended queue\n        int last = index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return array for printing */\n    vector<int> toVector() {\n        // Elements enqueue\n        vector<int> res(queSize);\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[index(j)];\n        }\n        return res;\n    }\n};\n
array_deque.java
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n    private int[] nums; // Array for storing double-ended queue elements\n    private int front; // Front pointer, points to the front of the queue element\n    private int queSize; // Double-ended queue length\n\n    /* Constructor */\n    public ArrayDeque(int capacity) {\n        this.nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the capacity of the double-ended queue */\n    public int capacity() {\n        return nums.length;\n    }\n\n    /* Get the length of the double-ended queue */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    public boolean isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate circular array index */\n    private int index(int i) {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + capacity()) % capacity();\n    }\n\n    /* Front of the queue enqueue */\n    public void pushFirst(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        front = index(front - 1);\n        // Add num to front of queue\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue enqueue */\n    public void pushLast(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        int rear = index(front + queSize);\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue dequeue */\n    public int popFirst() {\n        int num = peekFirst();\n        // Move front pointer backward by one position\n        front = index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Access rear of the queue element */\n    public int popLast() {\n        int num = peekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int peekFirst() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return nums[front];\n    }\n\n    /* Driver Code */\n    public int peekLast() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        // Initialize double-ended queue\n        int last = index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return array for printing */\n    public int[] toArray() {\n        // Elements enqueue\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[index(j)];\n        }\n        return res;\n    }\n}\n
array_deque.cs
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n    int[] nums;  // Array for storing double-ended queue elements\n    int front;   // Front pointer, points to the front of the queue element\n    int queSize; // Double-ended queue length\n\n    /* Constructor */\n    public ArrayDeque(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the capacity of the double-ended queue */\n    int Capacity() {\n        return nums.Length;\n    }\n\n    /* Get the length of the double-ended queue */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    public bool IsEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate circular array index */\n    int Index(int i) {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + Capacity()) % Capacity();\n    }\n\n    /* Front of the queue enqueue */\n    public void PushFirst(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        front = Index(front - 1);\n        // Add num to front of queue\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue enqueue */\n    public void PushLast(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        int rear = Index(front + queSize);\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Rear of the queue dequeue */\n    public int PopFirst() {\n        int num = PeekFirst();\n        // Move front pointer backward by one position\n        front = Index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Access rear of the queue element */\n    public int PopLast() {\n        int num = PeekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int PeekFirst() {\n        if (IsEmpty()) {\n            throw new InvalidOperationException();\n        }\n        return nums[front];\n    }\n\n    /* Driver Code */\n    public int PeekLast() {\n        if (IsEmpty()) {\n            throw new InvalidOperationException();\n        }\n        // Initialize double-ended queue\n        int last = Index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return array for printing */\n    public int[] ToArray() {\n        // Elements enqueue\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[Index(j)];\n        }\n        return res;\n    }\n}\n
array_deque.go
/* Double-ended queue based on circular array implementation */\ntype arrayDeque struct {\n    nums        []int // Array for storing double-ended queue elements\n    front       int   // Front pointer, points to the front of the queue element\n    queSize     int   // Double-ended queue length\n    queCapacity int   // Queue capacity (maximum number of elements)\n}\n\n/* Access front of the queue element */\nfunc newArrayDeque(queCapacity int) *arrayDeque {\n    return &arrayDeque{\n        nums:        make([]int, queCapacity),\n        queCapacity: queCapacity,\n        front:       0,\n        queSize:     0,\n    }\n}\n\n/* Get the length of the double-ended queue */\nfunc (q *arrayDeque) size() int {\n    return q.queSize\n}\n\n/* Check if the double-ended queue is empty */\nfunc (q *arrayDeque) isEmpty() bool {\n    return q.queSize == 0\n}\n\n/* Calculate circular array index */\nfunc (q *arrayDeque) index(i int) int {\n    // Use modulo operation to wrap the array head and tail together\n    // When i passes the tail of the array, return to the head\n    // When i passes the head of the array, return to the tail\n    return (i + q.queCapacity) % q.queCapacity\n}\n\n/* Front of the queue enqueue */\nfunc (q *arrayDeque) pushFirst(num int) {\n    if q.queSize == q.queCapacity {\n        fmt.Println(\"Double-ended queue is full\")\n        return\n    }\n    // Use modulo operation to wrap front around to the tail after passing the head of the array\n    // Add num to the front of the queue\n    q.front = q.index(q.front - 1)\n    // Add num to front of queue\n    q.nums[q.front] = num\n    q.queSize++\n}\n\n/* Rear of the queue enqueue */\nfunc (q *arrayDeque) pushLast(num int) {\n    if q.queSize == q.queCapacity {\n        fmt.Println(\"Double-ended queue is full\")\n        return\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    rear := q.index(q.front + q.queSize)\n    // Front pointer moves one position backward\n    q.nums[rear] = num\n    q.queSize++\n}\n\n/* Rear of the queue dequeue */\nfunc (q *arrayDeque) popFirst() any {\n    num := q.peekFirst()\n    if num == nil {\n        return nil\n    }\n    // Move front pointer backward by one position\n    q.front = q.index(q.front + 1)\n    q.queSize--\n    return num\n}\n\n/* Access rear of the queue element */\nfunc (q *arrayDeque) popLast() any {\n    num := q.peekLast()\n    if num == nil {\n        return nil\n    }\n    q.queSize--\n    return num\n}\n\n/* Return list for printing */\nfunc (q *arrayDeque) peekFirst() any {\n    if q.isEmpty() {\n        return nil\n    }\n    return q.nums[q.front]\n}\n\n/* Driver Code */\nfunc (q *arrayDeque) peekLast() any {\n    if q.isEmpty() {\n        return nil\n    }\n    // Initialize double-ended queue\n    last := q.index(q.front + q.queSize - 1)\n    return q.nums[last]\n}\n\n/* Get Slice for printing */\nfunc (q *arrayDeque) toSlice() []int {\n    // Elements enqueue\n    res := make([]int, q.queSize)\n    for i, j := 0, q.front; i < q.queSize; i++ {\n        res[i] = q.nums[q.index(j)]\n        j++\n    }\n    return res\n}\n
array_deque.swift
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n    private var nums: [Int] // Array for storing double-ended queue elements\n    private var front: Int // Front pointer, points to the front of the queue element\n    private var _size: Int // Double-ended queue length\n\n    /* Constructor */\n    init(capacity: Int) {\n        nums = Array(repeating: 0, count: capacity)\n        front = 0\n        _size = 0\n    }\n\n    /* Get the capacity of the double-ended queue */\n    func capacity() -> Int {\n        nums.count\n    }\n\n    /* Get the length of the double-ended queue */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check if the double-ended queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Calculate circular array index */\n    private func index(i: Int) -> Int {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        (i + capacity()) % capacity()\n    }\n\n    /* Front of the queue enqueue */\n    func pushFirst(num: Int) {\n        if size() == capacity() {\n            print(\"Double-ended queue is full\")\n            return\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        front = index(i: front - 1)\n        // Add num to front of queue\n        nums[front] = num\n        _size += 1\n    }\n\n    /* Rear of the queue enqueue */\n    func pushLast(num: Int) {\n        if size() == capacity() {\n            print(\"Double-ended queue is full\")\n            return\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        let rear = index(i: front + size())\n        // Front pointer moves one position backward\n        nums[rear] = num\n        _size += 1\n    }\n\n    /* Rear of the queue dequeue */\n    func popFirst() -> Int {\n        let num = peekFirst()\n        // Move front pointer backward by one position\n        front = index(i: front + 1)\n        _size -= 1\n        return num\n    }\n\n    /* Access rear of the queue element */\n    func popLast() -> Int {\n        let num = peekLast()\n        _size -= 1\n        return num\n    }\n\n    /* Return list for printing */\n    func peekFirst() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return nums[front]\n    }\n\n    /* Driver Code */\n    func peekLast() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        // Initialize double-ended queue\n        let last = index(i: front + size() - 1)\n        return nums[last]\n    }\n\n    /* Return array for printing */\n    func toArray() -> [Int] {\n        // Elements enqueue\n        (front ..< front + size()).map { nums[index(i: $0)] }\n    }\n}\n
array_deque.js
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n    #nums; // Array for storing double-ended queue elements\n    #front; // Front pointer, points to the front of the queue element\n    #queSize; // Double-ended queue length\n\n    /* Constructor */\n    constructor(capacity) {\n        this.#nums = new Array(capacity);\n        this.#front = 0;\n        this.#queSize = 0;\n    }\n\n    /* Get the capacity of the double-ended queue */\n    capacity() {\n        return this.#nums.length;\n    }\n\n    /* Get the length of the double-ended queue */\n    size() {\n        return this.#queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Calculate circular array index */\n    index(i) {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + this.capacity()) % this.capacity();\n    }\n\n    /* Front of the queue enqueue */\n    pushFirst(num) {\n        if (this.#queSize === this.capacity()) {\n            console.log('Double-ended queue is full');\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        this.#front = this.index(this.#front - 1);\n        // Add num to front of queue\n        this.#nums[this.#front] = num;\n        this.#queSize++;\n    }\n\n    /* Rear of the queue enqueue */\n    pushLast(num) {\n        if (this.#queSize === this.capacity()) {\n            console.log('Double-ended queue is full');\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        const rear = this.index(this.#front + this.#queSize);\n        // Front pointer moves one position backward\n        this.#nums[rear] = num;\n        this.#queSize++;\n    }\n\n    /* Rear of the queue dequeue */\n    popFirst() {\n        const num = this.peekFirst();\n        // Move front pointer backward by one position\n        this.#front = this.index(this.#front + 1);\n        this.#queSize--;\n        return num;\n    }\n\n    /* Access rear of the queue element */\n    popLast() {\n        const num = this.peekLast();\n        this.#queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peekFirst() {\n        if (this.isEmpty()) throw new Error('The Deque Is Empty.');\n        return this.#nums[this.#front];\n    }\n\n    /* Driver Code */\n    peekLast() {\n        if (this.isEmpty()) throw new Error('The Deque Is Empty.');\n        // Initialize double-ended queue\n        const last = this.index(this.#front + this.#queSize - 1);\n        return this.#nums[last];\n    }\n\n    /* Return array for printing */\n    toArray() {\n        // Elements enqueue\n        const res = [];\n        for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) {\n            res[i] = this.#nums[this.index(j)];\n        }\n        return res;\n    }\n}\n
array_deque.ts
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n    private nums: number[]; // Array for storing double-ended queue elements\n    private front: number; // Front pointer, points to the front of the queue element\n    private queSize: number; // Double-ended queue length\n\n    /* Constructor */\n    constructor(capacity: number) {\n        this.nums = new Array(capacity);\n        this.front = 0;\n        this.queSize = 0;\n    }\n\n    /* Get the capacity of the double-ended queue */\n    capacity(): number {\n        return this.nums.length;\n    }\n\n    /* Get the length of the double-ended queue */\n    size(): number {\n        return this.queSize;\n    }\n\n    /* Check if the double-ended queue is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Calculate circular array index */\n    index(i: number): number {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + this.capacity()) % this.capacity();\n    }\n\n    /* Front of the queue enqueue */\n    pushFirst(num: number): void {\n        if (this.queSize === this.capacity()) {\n            console.log('Double-ended queue is full');\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        this.front = this.index(this.front - 1);\n        // Add num to front of queue\n        this.nums[this.front] = num;\n        this.queSize++;\n    }\n\n    /* Rear of the queue enqueue */\n    pushLast(num: number): void {\n        if (this.queSize === this.capacity()) {\n            console.log('Double-ended queue is full');\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        const rear: number = this.index(this.front + this.queSize);\n        // Front pointer moves one position backward\n        this.nums[rear] = num;\n        this.queSize++;\n    }\n\n    /* Rear of the queue dequeue */\n    popFirst(): number {\n        const num: number = this.peekFirst();\n        // Move front pointer backward by one position\n        this.front = this.index(this.front + 1);\n        this.queSize--;\n        return num;\n    }\n\n    /* Access rear of the queue element */\n    popLast(): number {\n        const num: number = this.peekLast();\n        this.queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peekFirst(): number {\n        if (this.isEmpty()) throw new Error('The Deque Is Empty.');\n        return this.nums[this.front];\n    }\n\n    /* Driver Code */\n    peekLast(): number {\n        if (this.isEmpty()) throw new Error('The Deque Is Empty.');\n        // Initialize double-ended queue\n        const last = this.index(this.front + this.queSize - 1);\n        return this.nums[last];\n    }\n\n    /* Return array for printing */\n    toArray(): number[] {\n        // Elements enqueue\n        const res: number[] = [];\n        for (let i = 0, j = this.front; i < this.queSize; i++, j++) {\n            res[i] = this.nums[this.index(j)];\n        }\n        return res;\n    }\n}\n
array_deque.dart
/* Double-ended queue based on circular array implementation */\nclass ArrayDeque {\n  late List<int> _nums; // Array for storing double-ended queue elements\n  late int _front; // Front pointer, points to the front of the queue element\n  late int _queSize; // Double-ended queue length\n\n  /* Constructor */\n  ArrayDeque(int capacity) {\n    this._nums = List.filled(capacity, 0);\n    this._front = this._queSize = 0;\n  }\n\n  /* Get the capacity of the double-ended queue */\n  int capacity() {\n    return _nums.length;\n  }\n\n  /* Get the length of the double-ended queue */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check if the double-ended queue is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Calculate circular array index */\n  int index(int i) {\n    // Use modulo operation to wrap the array head and tail together\n    // When i passes the tail of the array, return to the head\n    // When i passes the head of the array, return to the tail\n    return (i + capacity()) % capacity();\n  }\n\n  /* Front of the queue enqueue */\n  void pushFirst(int _num) {\n    if (_queSize == capacity()) {\n      throw Exception(\"Double-ended queue is full\");\n    }\n    // Use modulo operation to wrap front around to the tail after passing the head of the array\n    // Use modulo operation to wrap _front from array head back to tail\n    _front = index(_front - 1);\n    // Add _num to queue front\n    _nums[_front] = _num;\n    _queSize++;\n  }\n\n  /* Rear of the queue enqueue */\n  void pushLast(int _num) {\n    if (_queSize == capacity()) {\n      throw Exception(\"Double-ended queue is full\");\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    int rear = index(_front + _queSize);\n    // Add _num to queue rear\n    _nums[rear] = _num;\n    _queSize++;\n  }\n\n  /* Rear of the queue dequeue */\n  int popFirst() {\n    int _num = peekFirst();\n    // Move front pointer right by one\n    _front = index(_front + 1);\n    _queSize--;\n    return _num;\n  }\n\n  /* Access rear of the queue element */\n  int popLast() {\n    int _num = peekLast();\n    _queSize--;\n    return _num;\n  }\n\n  /* Return list for printing */\n  int peekFirst() {\n    if (isEmpty()) {\n      throw Exception(\"Deque is empty\");\n    }\n    return _nums[_front];\n  }\n\n  /* Driver Code */\n  int peekLast() {\n    if (isEmpty()) {\n      throw Exception(\"Deque is empty\");\n    }\n    // Initialize double-ended queue\n    int last = index(_front + _queSize - 1);\n    return _nums[last];\n  }\n\n  /* Return array for printing */\n  List<int> toArray() {\n    // Elements enqueue\n    List<int> res = List.filled(_queSize, 0);\n    for (int i = 0, j = _front; i < _queSize; i++, j++) {\n      res[i] = _nums[index(j)];\n    }\n    return res;\n  }\n}\n
array_deque.rs
/* Double-ended queue based on circular array implementation */\nstruct ArrayDeque<T> {\n    nums: Vec<T>,    // Array for storing double-ended queue elements\n    front: usize,    // Front pointer, points to the front of the queue element\n    que_size: usize, // Double-ended queue length\n}\n\nimpl<T: Copy + Default> ArrayDeque<T> {\n    /* Constructor */\n    pub fn new(capacity: usize) -> Self {\n        Self {\n            nums: vec![T::default(); capacity],\n            front: 0,\n            que_size: 0,\n        }\n    }\n\n    /* Get the capacity of the double-ended queue */\n    pub fn capacity(&self) -> usize {\n        self.nums.len()\n    }\n\n    /* Get the length of the double-ended queue */\n    pub fn size(&self) -> usize {\n        self.que_size\n    }\n\n    /* Check if the double-ended queue is empty */\n    pub fn is_empty(&self) -> bool {\n        self.que_size == 0\n    }\n\n    /* Calculate circular array index */\n    fn index(&self, i: i32) -> usize {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        ((i + self.capacity() as i32) % self.capacity() as i32) as usize\n    }\n\n    /* Front of the queue enqueue */\n    pub fn push_first(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        self.front = self.index(self.front as i32 - 1);\n        // Add num to front of queue\n        self.nums[self.front] = num;\n        self.que_size += 1;\n    }\n\n    /* Rear of the queue enqueue */\n    pub fn push_last(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Double-ended queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        let rear = self.index(self.front as i32 + self.que_size as i32);\n        // Front pointer moves one position backward\n        self.nums[rear] = num;\n        self.que_size += 1;\n    }\n\n    /* Rear of the queue dequeue */\n    fn pop_first(&mut self) -> T {\n        let num = self.peek_first();\n        // Move front pointer backward by one position\n        self.front = self.index(self.front as i32 + 1);\n        self.que_size -= 1;\n        num\n    }\n\n    /* Access rear of the queue element */\n    fn pop_last(&mut self) -> T {\n        let num = self.peek_last();\n        self.que_size -= 1;\n        num\n    }\n\n    /* Return list for printing */\n    fn peek_first(&self) -> T {\n        if self.is_empty() {\n            panic!(\"Deque is empty\")\n        };\n        self.nums[self.front]\n    }\n\n    /* Driver Code */\n    fn peek_last(&self) -> T {\n        if self.is_empty() {\n            panic!(\"Deque is empty\")\n        };\n        // Initialize double-ended queue\n        let last = self.index(self.front as i32 + self.que_size as i32 - 1);\n        self.nums[last]\n    }\n\n    /* Return array for printing */\n    fn to_array(&self) -> Vec<T> {\n        // Elements enqueue\n        let mut res = vec![T::default(); self.que_size];\n        let mut j = self.front;\n        for i in 0..self.que_size {\n            res[i] = self.nums[self.index(j as i32)];\n            j += 1;\n        }\n        res\n    }\n}\n
array_deque.c
/* Double-ended queue based on circular array implementation */\ntypedef struct {\n    int *nums;       // Array for storing queue elements\n    int front;       // Front pointer, points to the front of the queue element\n    int queSize;     // Rear pointer, points to rear + 1\n    int queCapacity; // Queue capacity\n} ArrayDeque;\n\n/* Constructor */\nArrayDeque *newArrayDeque(int capacity) {\n    ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque));\n    // Initialize array\n    deque->queCapacity = capacity;\n    deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity);\n    deque->front = deque->queSize = 0;\n    return deque;\n}\n\n/* Destructor */\nvoid delArrayDeque(ArrayDeque *deque) {\n    free(deque->nums);\n    free(deque);\n}\n\n/* Get the capacity of the double-ended queue */\nint capacity(ArrayDeque *deque) {\n    return deque->queCapacity;\n}\n\n/* Get the length of the double-ended queue */\nint size(ArrayDeque *deque) {\n    return deque->queSize;\n}\n\n/* Check if the double-ended queue is empty */\nbool empty(ArrayDeque *deque) {\n    return deque->queSize == 0;\n}\n\n/* Calculate circular array index */\nint dequeIndex(ArrayDeque *deque, int i) {\n    // Use modulo operation to wrap the array head and tail together\n    // When i exceeds array end, wrap to head\n    // When i passes the head of the array, return to the tail\n    return ((i + capacity(deque)) % capacity(deque));\n}\n\n/* Front of the queue enqueue */\nvoid pushFirst(ArrayDeque *deque, int num) {\n    if (deque->queSize == capacity(deque)) {\n        printf(\"Deque is full\\r\\n\");\n        return;\n    }\n    // Use modulo operation to wrap front around to the tail after passing the head of the array\n    // Use modulo to wrap front from array head to rear\n    deque->front = dequeIndex(deque, deque->front - 1);\n    // Add num to queue front\n    deque->nums[deque->front] = num;\n    deque->queSize++;\n}\n\n/* Rear of the queue enqueue */\nvoid pushLast(ArrayDeque *deque, int num) {\n    if (deque->queSize == capacity(deque)) {\n        printf(\"Deque is full\\r\\n\");\n        return;\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    int rear = dequeIndex(deque, deque->front + deque->queSize);\n    // Front pointer moves one position backward\n    deque->nums[rear] = num;\n    deque->queSize++;\n}\n\n/* Return list for printing */\nint peekFirst(ArrayDeque *deque) {\n    // Access error: Deque is empty\n    assert(empty(deque) == 0);\n    return deque->nums[deque->front];\n}\n\n/* Driver Code */\nint peekLast(ArrayDeque *deque) {\n    // Access error: Deque is empty\n    assert(empty(deque) == 0);\n    int last = dequeIndex(deque, deque->front + deque->queSize - 1);\n    return deque->nums[last];\n}\n\n/* Rear of the queue dequeue */\nint popFirst(ArrayDeque *deque) {\n    int num = peekFirst(deque);\n    // Move front pointer backward by one position\n    deque->front = dequeIndex(deque, deque->front + 1);\n    deque->queSize--;\n    return num;\n}\n\n/* Access rear of the queue element */\nint popLast(ArrayDeque *deque) {\n    int num = peekLast(deque);\n    deque->queSize--;\n    return num;\n}\n\n/* Return array for printing */\nint *toArray(ArrayDeque *deque, int *queSize) {\n    *queSize = deque->queSize;\n    int *res = (int *)calloc(deque->queSize, sizeof(int));\n    int j = deque->front;\n    for (int i = 0; i < deque->queSize; i++) {\n        res[i] = deque->nums[j % deque->queCapacity];\n        j++;\n    }\n    return res;\n}\n
array_deque.kt
/* Constructor */\nclass ArrayDeque(capacity: Int) {\n    private var nums: IntArray = IntArray(capacity) // Array for storing double-ended queue elements\n    private var front: Int = 0 // Front pointer, points to the front of the queue element\n    private var queSize: Int = 0 // Double-ended queue length\n\n    /* Get the capacity of the double-ended queue */\n    fun capacity(): Int {\n        return nums.size\n    }\n\n    /* Get the length of the double-ended queue */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check if the double-ended queue is empty */\n    fun isEmpty(): Boolean {\n        return queSize == 0\n    }\n\n    /* Calculate circular array index */\n    private fun index(i: Int): Int {\n        // Use modulo operation to wrap the array head and tail together\n        // When i passes the tail of the array, return to the head\n        // When i passes the head of the array, return to the tail\n        return (i + capacity()) % capacity()\n    }\n\n    /* Front of the queue enqueue */\n    fun pushFirst(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Double-ended queue is full\")\n            return\n        }\n        // Use modulo operation to wrap front around to the tail after passing the head of the array\n        // Add num to the front of the queue\n        front = index(front - 1)\n        // Add num to front of queue\n        nums[front] = num\n        queSize++\n    }\n\n    /* Rear of the queue enqueue */\n    fun pushLast(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Double-ended queue is full\")\n            return\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        val rear = index(front + queSize)\n        // Front pointer moves one position backward\n        nums[rear] = num\n        queSize++\n    }\n\n    /* Rear of the queue dequeue */\n    fun popFirst(): Int {\n        val num = peekFirst()\n        // Move front pointer backward by one position\n        front = index(front + 1)\n        queSize--\n        return num\n    }\n\n    /* Access rear of the queue element */\n    fun popLast(): Int {\n        val num = peekLast()\n        queSize--\n        return num\n    }\n\n    /* Return list for printing */\n    fun peekFirst(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return nums[front]\n    }\n\n    /* Driver Code */\n    fun peekLast(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        // Initialize double-ended queue\n        val last = index(front + queSize - 1)\n        return nums[last]\n    }\n\n    /* Return array for printing */\n    fun toArray(): IntArray {\n        // Elements enqueue\n        val res = IntArray(queSize)\n        var i = 0\n        var j = front\n        while (i < queSize) {\n            res[i] = nums[index(j)]\n            i++\n            j++\n        }\n        return res\n    }\n}\n
array_deque.rb
### Deque based on circular array ###\nclass ArrayDeque\n  ### Get deque length ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize(capacity)\n    @nums = Array.new(capacity, 0)\n    @front = 0\n    @size = 0\n  end\n\n  ### Get deque capacity ###\n  def capacity\n    @nums.length\n  end\n\n  ### Check if deque is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue at front ###\n  def push_first(num)\n    if size == capacity\n      puts 'Double-ended queue is full'\n      return\n    end\n\n    # Use modulo operation to wrap front around to the tail after passing the head of the array\n    # Add num to the front of the queue\n    @front = index(@front - 1)\n    # Add num to front of queue\n    @nums[@front] = num\n    @size += 1\n  end\n\n  ### Enqueue at rear ###\n  def push_last(num)\n    if size == capacity\n      puts 'Double-ended queue is full'\n      return\n    end\n\n    # Use modulo operation to wrap rear around to the head after passing the tail of the array\n    rear = index(@front + size)\n    # Front pointer moves one position backward\n    @nums[rear] = num\n    @size += 1\n  end\n\n  ### Dequeue from front ###\n  def pop_first\n    num = peek_first\n    # Move front pointer backward by one position\n    @front = index(@front + 1)\n    @size -= 1\n    num\n  end\n\n  ### Dequeue from rear ###\n  def pop_last\n    num = peek_last\n    @size -= 1\n    num\n  end\n\n  ### Access front element ###\n  def peek_first\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @nums[@front]\n  end\n\n  ### Access rear element ###\n  def peek_last\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    # Initialize double-ended queue\n    last = index(@front + size - 1)\n    @nums[last]\n  end\n\n  ### Return array for printing ###\n  def to_array\n    # Elements enqueue\n    res = []\n    for i in 0...size\n      res << @nums[index(@front + i)]\n    end\n    res\n  end\n\n  private\n\n  ### Calculate circular array index ###\n  def index(i)\n    # Use modulo operation to wrap the array head and tail together\n    # When i passes the tail of the array, return to the head\n    # When i passes the head of the array, return to the tail\n    (i + capacity) % capacity\n  end\nend\n
","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#533-deque-applications","level":2,"title":"5.3.3   Deque Applications","text":"

A deque combines the logic of both stacks and queues. Therefore, it can implement all application scenarios of both, while providing greater flexibility.

We know that the \"undo\" function in software is typically implemented using a stack: the system pushes each change operation onto the stack and then implements undo through pop. However, considering system resource limitations, software usually limits the number of undo steps (for example, only allowing 50 steps to be saved). When the stack length exceeds 50, the software needs to perform a deletion operation at the bottom of the stack (front of the queue). But a stack cannot implement this functionality, so a deque is needed to replace the stack. Note that the core logic of \"undo\" still follows the LIFO principle of a stack; it's just that the deque can more flexibly implement some additional logic.

","path":["Chapter 5. Stack and Queue","5.3   Deque"],"tags":[]},{"location":"chapter_stack_and_queue/queue/","level":1,"title":"5.2   Queue","text":"

A queue is a linear data structure that follows the First In First Out (FIFO) rule. As the name suggests, a queue simulates the phenomenon of lining up, where newcomers continuously join the end of the queue, while people at the front of the queue leave one by one.

As shown in Figure 5-4, we call the front of the queue the \"front\" and the end the \"rear.\" The operation of adding an element to the rear is called \"enqueue,\" and the operation of removing the front element is called \"dequeue.\"

Figure 5-4   FIFO rule of queue

","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#521-common-queue-operations","level":2,"title":"5.2.1   Common Queue Operations","text":"

The common operations on a queue are shown in Table 5-2. Note that method names may vary across different programming languages. We adopt the same naming convention as for stacks here.

Table 5-2   Efficiency of Queue Operations

Method Description Time Complexity push() Enqueue element, add element to rear \\(O(1)\\) pop() Dequeue front element \\(O(1)\\) peek() Access front element \\(O(1)\\)

We can directly use the ready-made queue classes in programming languages:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby queue.py
from collections import deque\n\n# Initialize queue\n# In Python, we generally use the deque class as a queue\n# Although queue.Queue() is a pure queue class, it is not very user-friendly, so it is not recommended\nque: deque[int] = deque()\n\n# Enqueue elements\nque.append(1)\nque.append(3)\nque.append(2)\nque.append(5)\nque.append(4)\n\n# Access front element\nfront: int = que[0]\n\n# Dequeue element\npop: int = que.popleft()\n\n# Get queue length\nsize: int = len(que)\n\n# Check if queue is empty\nis_empty: bool = len(que) == 0\n
queue.cpp
/* Initialize queue */\nqueue<int> queue;\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access front element */\nint front = queue.front();\n\n/* Dequeue element */\nqueue.pop();\n\n/* Get queue length */\nint size = queue.size();\n\n/* Check if queue is empty */\nbool empty = queue.empty();\n
queue.java
/* Initialize queue */\nQueue<Integer> queue = new LinkedList<>();\n\n/* Enqueue elements */\nqueue.offer(1);\nqueue.offer(3);\nqueue.offer(2);\nqueue.offer(5);\nqueue.offer(4);\n\n/* Access front element */\nint peek = queue.peek();\n\n/* Dequeue element */\nint pop = queue.poll();\n\n/* Get queue length */\nint size = queue.size();\n\n/* Check if queue is empty */\nboolean isEmpty = queue.isEmpty();\n
queue.cs
/* Initialize queue */\nQueue<int> queue = new();\n\n/* Enqueue elements */\nqueue.Enqueue(1);\nqueue.Enqueue(3);\nqueue.Enqueue(2);\nqueue.Enqueue(5);\nqueue.Enqueue(4);\n\n/* Access front element */\nint peek = queue.Peek();\n\n/* Dequeue element */\nint pop = queue.Dequeue();\n\n/* Get queue length */\nint size = queue.Count;\n\n/* Check if queue is empty */\nbool isEmpty = queue.Count == 0;\n
queue_test.go
/* Initialize queue */\n// In Go, use list as a queue\nqueue := list.New()\n\n/* Enqueue elements */\nqueue.PushBack(1)\nqueue.PushBack(3)\nqueue.PushBack(2)\nqueue.PushBack(5)\nqueue.PushBack(4)\n\n/* Access front element */\npeek := queue.Front()\n\n/* Dequeue element */\npop := queue.Front()\nqueue.Remove(pop)\n\n/* Get queue length */\nsize := queue.Len()\n\n/* Check if queue is empty */\nisEmpty := queue.Len() == 0\n
queue.swift
/* Initialize queue */\n// Swift does not have a built-in queue class, can use Array as a queue\nvar queue: [Int] = []\n\n/* Enqueue elements */\nqueue.append(1)\nqueue.append(3)\nqueue.append(2)\nqueue.append(5)\nqueue.append(4)\n\n/* Access front element */\nlet peek = queue.first!\n\n/* Dequeue element */\n// Since it's an array, removeFirst has O(n) complexity\nlet pool = queue.removeFirst()\n\n/* Get queue length */\nlet size = queue.count\n\n/* Check if queue is empty */\nlet isEmpty = queue.isEmpty\n
queue.js
/* Initialize queue */\n// JavaScript does not have a built-in queue, can use Array as a queue\nconst queue = [];\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access front element */\nconst peek = queue[0];\n\n/* Dequeue element */\n// The underlying structure is an array, so shift() has O(n) time complexity\nconst pop = queue.shift();\n\n/* Get queue length */\nconst size = queue.length;\n\n/* Check if queue is empty */\nconst empty = queue.length === 0;\n
queue.ts
/* Initialize queue */\n// TypeScript does not have a built-in queue, can use Array as a queue\nconst queue: number[] = [];\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access front element */\nconst peek = queue[0];\n\n/* Dequeue element */\n// The underlying structure is an array, so shift() has O(n) time complexity\nconst pop = queue.shift();\n\n/* Get queue length */\nconst size = queue.length;\n\n/* Check if queue is empty */\nconst empty = queue.length === 0;\n
queue.dart
/* Initialize queue */\n// In Dart, the Queue class is a deque and can also be used as a queue\nQueue<int> queue = Queue();\n\n/* Enqueue elements */\nqueue.add(1);\nqueue.add(3);\nqueue.add(2);\nqueue.add(5);\nqueue.add(4);\n\n/* Access front element */\nint peek = queue.first;\n\n/* Dequeue element */\nint pop = queue.removeFirst();\n\n/* Get queue length */\nint size = queue.length;\n\n/* Check if queue is empty */\nbool isEmpty = queue.isEmpty;\n
queue.rs
/* Initialize deque */\n// In Rust, use deque as a regular queue\nlet mut deque: VecDeque<u32> = VecDeque::new();\n\n/* Enqueue elements */\ndeque.push_back(1);\ndeque.push_back(3);\ndeque.push_back(2);\ndeque.push_back(5);\ndeque.push_back(4);\n\n/* Access front element */\nif let Some(front) = deque.front() {\n}\n\n/* Dequeue element */\nif let Some(pop) = deque.pop_front() {\n}\n\n/* Get queue length */\nlet size = deque.len();\n\n/* Check if queue is empty */\nlet is_empty = deque.is_empty();\n
queue.c
// C does not provide a built-in queue\n
queue.kt
/* Initialize queue */\nval queue = LinkedList<Int>()\n\n/* Enqueue elements */\nqueue.offer(1)\nqueue.offer(3)\nqueue.offer(2)\nqueue.offer(5)\nqueue.offer(4)\n\n/* Access front element */\nval peek = queue.peek()\n\n/* Dequeue element */\nval pop = queue.poll()\n\n/* Get queue length */\nval size = queue.size\n\n/* Check if queue is empty */\nval isEmpty = queue.isEmpty()\n
queue.rb
# Initialize queue\n# Ruby's built-in queue (Thread::Queue) does not have peek and traversal methods, can use Array as a queue\nqueue = []\n\n# Enqueue elements\nqueue.push(1)\nqueue.push(3)\nqueue.push(2)\nqueue.push(5)\nqueue.push(4)\n\n# Access front element\npeek = queue.first\n\n# Dequeue element\n# Please note that since it's an array, Array#shift has O(n) time complexity\npop = queue.shift\n\n# Get queue length\nsize = queue.length\n\n# Check if queue is empty\nis_empty = queue.empty?\n
Code Visualization

Full Screen >

","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#522-queue-implementation","level":2,"title":"5.2.2   Queue Implementation","text":"

To implement a queue, we need a data structure that allows adding elements at one end and removing elements at the other end. Both linked lists and arrays meet this requirement.

","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#1-linked-list-implementation","level":3,"title":"1.   Linked List Implementation","text":"

As shown in Figure 5-5, we can treat the \"head node\" and \"tail node\" of a linked list as the \"front\" and \"rear\" of the queue, respectively, with the rule that nodes can only be added at the rear and removed from the front.

LinkedListQueuepush()pop()

Figure 5-5   Enqueue and dequeue operations in linked list implementation of queue

Below is the code for implementing a queue using a linked list:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_queue.py
class LinkedListQueue:\n    \"\"\"Queue based on linked list implementation\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._front: ListNode | None = None  # Head node front\n        self._rear: ListNode | None = None  # Tail node rear\n        self._size: int = 0\n\n    def size(self) -> int:\n        \"\"\"Get the length of the queue\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the queue is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int):\n        \"\"\"Enqueue\"\"\"\n        # Add num after the tail node\n        node = ListNode(num)\n        # If the queue is empty, make both front and rear point to the node\n        if self._front is None:\n            self._front = node\n            self._rear = node\n        # If the queue is not empty, add the node after the tail node\n        else:\n            self._rear.next = node\n            self._rear = node\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Dequeue\"\"\"\n        num = self.peek()\n        # Delete head node\n        self._front = self._front.next\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access front of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Queue is empty\")\n        return self._front.val\n\n    def to_list(self) -> list[int]:\n        \"\"\"Convert to list for printing\"\"\"\n        queue = []\n        temp = self._front\n        while temp:\n            queue.append(temp.val)\n            temp = temp.next\n        return queue\n
linkedlist_queue.cpp
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n  private:\n    ListNode *front, *rear; // Head node front, tail node rear\n    int queSize;\n\n  public:\n    LinkedListQueue() {\n        front = nullptr;\n        rear = nullptr;\n        queSize = 0;\n    }\n\n    ~LinkedListQueue() {\n        // Traverse linked list to delete nodes and free memory\n        freeMemoryLinkedList(front);\n    }\n\n    /* Get the length of the queue */\n    int size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    bool isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    void push(int num) {\n        // Add num after the tail node\n        ListNode *node = new ListNode(num);\n        // If the queue is empty, make both front and rear point to the node\n        if (front == nullptr) {\n            front = node;\n            rear = node;\n        }\n        // If the queue is not empty, add the node after the tail node\n        else {\n            rear->next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    int pop() {\n        int num = peek();\n        // Delete head node\n        ListNode *tmp = front;\n        front = front->next;\n        // Free memory\n        delete tmp;\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    int peek() {\n        if (size() == 0)\n            throw out_of_range(\"Queue is empty\");\n        return front->val;\n    }\n\n    /* Convert linked list to Vector and return */\n    vector<int> toVector() {\n        ListNode *node = front;\n        vector<int> res(size());\n        for (int i = 0; i < res.size(); i++) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
linkedlist_queue.java
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n    private ListNode front, rear; // Head node front, tail node rear\n    private int queSize = 0;\n\n    public LinkedListQueue() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the length of the queue */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue */\n    public void push(int num) {\n        // Add num after the tail node\n        ListNode node = new ListNode(num);\n        // If the queue is empty, make both front and rear point to the node\n        if (front == null) {\n            front = node;\n            rear = node;\n        // If the queue is not empty, add the node after the tail node\n        } else {\n            rear.next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int pop() {\n        int num = peek();\n        // Delete head node\n        front = front.next;\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return front.val;\n    }\n\n    /* Convert linked list to Array and return */\n    public int[] toArray() {\n        ListNode node = front;\n        int[] res = new int[size()];\n        for (int i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_queue.cs
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n    ListNode? front, rear;  // Head node front, tail node rear\n    int queSize = 0;\n\n    public LinkedListQueue() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the length of the queue */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Enqueue */\n    public void Push(int num) {\n        // Add num after the tail node\n        ListNode node = new(num);\n        // If the queue is empty, make both front and rear point to the node\n        if (front == null) {\n            front = node;\n            rear = node;\n            // If the queue is not empty, add the node after the tail node\n        } else if (rear != null) {\n            rear.next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int Pop() {\n        int num = Peek();\n        // Delete head node\n        front = front?.next;\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return front!.val;\n    }\n\n    /* Convert linked list to Array and return */\n    public int[] ToArray() {\n        if (front == null)\n            return [];\n\n        ListNode? node = front;\n        int[] res = new int[Size()];\n        for (int i = 0; i < res.Length; i++) {\n            res[i] = node!.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_queue.go
/* Queue based on linked list implementation */\ntype linkedListQueue struct {\n    // Use built-in package list to implement queue\n    data *list.List\n}\n\n/* Access front of the queue element */\nfunc newLinkedListQueue() *linkedListQueue {\n    return &linkedListQueue{\n        data: list.New(),\n    }\n}\n\n/* Enqueue */\nfunc (s *linkedListQueue) push(value any) {\n    s.data.PushBack(value)\n}\n\n/* Dequeue */\nfunc (s *linkedListQueue) pop() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Return list for printing */\nfunc (s *linkedListQueue) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    return e.Value\n}\n\n/* Get the length of the queue */\nfunc (s *linkedListQueue) size() int {\n    return s.data.Len()\n}\n\n/* Check if the queue is empty */\nfunc (s *linkedListQueue) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get List for printing */\nfunc (s *linkedListQueue) toList() *list.List {\n    return s.data\n}\n
linkedlist_queue.swift
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n    private var front: ListNode? // Head node\n    private var rear: ListNode? // Tail node\n    private var _size: Int\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the length of the queue */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check if the queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue */\n    func push(num: Int) {\n        // Add num after the tail node\n        let node = ListNode(x: num)\n        // If the queue is empty, make both front and rear point to the node\n        if front == nil {\n            front = node\n            rear = node\n        }\n        // If the queue is not empty, add the node after the tail node\n        else {\n            rear?.next = node\n            rear = node\n        }\n        _size += 1\n    }\n\n    /* Dequeue */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        // Delete head node\n        front = front?.next\n        _size -= 1\n        return num\n    }\n\n    /* Return list for printing */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Queue is empty\")\n        }\n        return front!.val\n    }\n\n    /* Convert linked list to Array and return */\n    func toArray() -> [Int] {\n        var node = front\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
linkedlist_queue.js
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n    #front; // Front node #front\n    #rear; // Rear node #rear\n    #queSize = 0;\n\n    constructor() {\n        this.#front = null;\n        this.#rear = null;\n    }\n\n    /* Get the length of the queue */\n    get size() {\n        return this.#queSize;\n    }\n\n    /* Check if the queue is empty */\n    isEmpty() {\n        return this.size === 0;\n    }\n\n    /* Enqueue */\n    push(num) {\n        // Add num after the tail node\n        const node = new ListNode(num);\n        // If the queue is empty, make both front and rear point to the node\n        if (!this.#front) {\n            this.#front = node;\n            this.#rear = node;\n            // If the queue is not empty, add the node after the tail node\n        } else {\n            this.#rear.next = node;\n            this.#rear = node;\n        }\n        this.#queSize++;\n    }\n\n    /* Dequeue */\n    pop() {\n        const num = this.peek();\n        // Delete head node\n        this.#front = this.#front.next;\n        this.#queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek() {\n        if (this.size === 0) throw new Error('Queue is empty');\n        return this.#front.val;\n    }\n\n    /* Convert linked list to Array and return */\n    toArray() {\n        let node = this.#front;\n        const res = new Array(this.size);\n        for (let i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_queue.ts
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n    private front: ListNode | null; // Head node front\n    private rear: ListNode | null; // Tail node rear\n    private queSize: number = 0;\n\n    constructor() {\n        this.front = null;\n        this.rear = null;\n    }\n\n    /* Get the length of the queue */\n    get size(): number {\n        return this.queSize;\n    }\n\n    /* Check if the queue is empty */\n    isEmpty(): boolean {\n        return this.size === 0;\n    }\n\n    /* Enqueue */\n    push(num: number): void {\n        // Add num after the tail node\n        const node = new ListNode(num);\n        // If the queue is empty, make both front and rear point to the node\n        if (!this.front) {\n            this.front = node;\n            this.rear = node;\n            // If the queue is not empty, add the node after the tail node\n        } else {\n            this.rear!.next = node;\n            this.rear = node;\n        }\n        this.queSize++;\n    }\n\n    /* Dequeue */\n    pop(): number {\n        const num = this.peek();\n        if (!this.front) throw new Error('Queue is empty');\n        // Delete head node\n        this.front = this.front.next;\n        this.queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek(): number {\n        if (this.size === 0) throw new Error('Queue is empty');\n        return this.front!.val;\n    }\n\n    /* Convert linked list to Array and return */\n    toArray(): number[] {\n        let node = this.front;\n        const res = new Array<number>(this.size);\n        for (let i = 0; i < res.length; i++) {\n            res[i] = node!.val;\n            node = node!.next;\n        }\n        return res;\n    }\n}\n
linkedlist_queue.dart
/* Queue based on linked list implementation */\nclass LinkedListQueue {\n  ListNode? _front; // Head node _front\n  ListNode? _rear; // Tail node _rear\n  int _queSize = 0; // Queue length\n\n  LinkedListQueue() {\n    _front = null;\n    _rear = null;\n  }\n\n  /* Get the length of the queue */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check if the queue is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Enqueue */\n  void push(int _num) {\n    // Add _num after tail node\n    final node = ListNode(_num);\n    // If the queue is empty, make both front and rear point to the node\n    if (_front == null) {\n      _front = node;\n      _rear = node;\n    } else {\n      // If the queue is not empty, add the node after the tail node\n      _rear!.next = node;\n      _rear = node;\n    }\n    _queSize++;\n  }\n\n  /* Dequeue */\n  int pop() {\n    final int _num = peek();\n    // Delete head node\n    _front = _front!.next;\n    _queSize--;\n    return _num;\n  }\n\n  /* Return list for printing */\n  int peek() {\n    if (_queSize == 0) {\n      throw Exception('Queue is empty');\n    }\n    return _front!.val;\n  }\n\n  /* Convert linked list to Array and return */\n  List<int> toArray() {\n    ListNode? node = _front;\n    final List<int> queue = [];\n    while (node != null) {\n      queue.add(node.val);\n      node = node.next;\n    }\n    return queue;\n  }\n}\n
linkedlist_queue.rs
/* Queue based on linked list implementation */\n#[allow(dead_code)]\npub struct LinkedListQueue<T> {\n    front: Option<Rc<RefCell<ListNode<T>>>>, // Head node front\n    rear: Option<Rc<RefCell<ListNode<T>>>>,  // Tail node rear\n    que_size: usize,                         // Queue length\n}\n\nimpl<T: Copy> LinkedListQueue<T> {\n    pub fn new() -> Self {\n        Self {\n            front: None,\n            rear: None,\n            que_size: 0,\n        }\n    }\n\n    /* Get the length of the queue */\n    pub fn size(&self) -> usize {\n        return self.que_size;\n    }\n\n    /* Check if the queue is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.que_size == 0;\n    }\n\n    /* Enqueue */\n    pub fn push(&mut self, num: T) {\n        // Add num after the tail node\n        let new_rear = ListNode::new(num);\n        match self.rear.take() {\n            // If the queue is not empty, add the node after the tail node\n            Some(old_rear) => {\n                old_rear.borrow_mut().next = Some(new_rear.clone());\n                self.rear = Some(new_rear);\n            }\n            // If the queue is empty, make both front and rear point to the node\n            None => {\n                self.front = Some(new_rear.clone());\n                self.rear = Some(new_rear);\n            }\n        }\n        self.que_size += 1;\n    }\n\n    /* Dequeue */\n    pub fn pop(&mut self) -> Option<T> {\n        self.front.take().map(|old_front| {\n            match old_front.borrow_mut().next.take() {\n                Some(new_front) => {\n                    self.front = Some(new_front);\n                }\n                None => {\n                    self.rear.take();\n                }\n            }\n            self.que_size -= 1;\n            old_front.borrow().val\n        })\n    }\n\n    /* Return list for printing */\n    pub fn peek(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.front.as_ref()\n    }\n\n    /* Convert linked list to Array and return */\n    pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n        let mut res: Vec<T> = Vec::new();\n\n        fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {\n            if let Some(cur) = cur {\n                res.push(cur.borrow().val);\n                recur(cur.borrow().next.as_ref(), res);\n            }\n        }\n\n        recur(head, &mut res);\n\n        res\n    }\n}\n
linkedlist_queue.c
/* Queue based on linked list implementation */\ntypedef struct {\n    ListNode *front, *rear;\n    int queSize;\n} LinkedListQueue;\n\n/* Constructor */\nLinkedListQueue *newLinkedListQueue() {\n    LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue));\n    queue->front = NULL;\n    queue->rear = NULL;\n    queue->queSize = 0;\n    return queue;\n}\n\n/* Destructor */\nvoid delLinkedListQueue(LinkedListQueue *queue) {\n    // Free all nodes\n    while (queue->front != NULL) {\n        ListNode *tmp = queue->front;\n        queue->front = queue->front->next;\n        free(tmp);\n    }\n    // Free queue structure\n    free(queue);\n}\n\n/* Get the length of the queue */\nint size(LinkedListQueue *queue) {\n    return queue->queSize;\n}\n\n/* Check if the queue is empty */\nbool empty(LinkedListQueue *queue) {\n    return (size(queue) == 0);\n}\n\n/* Enqueue */\nvoid push(LinkedListQueue *queue, int num) {\n    // Add node at tail\n    ListNode *node = newListNode(num);\n    // If the queue is empty, make both front and rear point to the node\n    if (queue->front == NULL) {\n        queue->front = node;\n        queue->rear = node;\n    }\n    // If the queue is not empty, add the node after the tail node\n    else {\n        queue->rear->next = node;\n        queue->rear = node;\n    }\n    queue->queSize++;\n}\n\n/* Return list for printing */\nint peek(LinkedListQueue *queue) {\n    assert(size(queue) && queue->front);\n    return queue->front->val;\n}\n\n/* Dequeue */\nint pop(LinkedListQueue *queue) {\n    int num = peek(queue);\n    ListNode *tmp = queue->front;\n    queue->front = queue->front->next;\n    free(tmp);\n    queue->queSize--;\n    return num;\n}\n\n/* Print queue */\nvoid printLinkedListQueue(LinkedListQueue *queue) {\n    int *arr = malloc(sizeof(int) * queue->queSize);\n    // Copy data from list to array\n    int i;\n    ListNode *node;\n    for (i = 0, node = queue->front; i < queue->queSize; i++) {\n        arr[i] = node->val;\n        node = node->next;\n    }\n    printArray(arr, queue->queSize);\n    free(arr);\n}\n
linkedlist_queue.kt
/* Queue based on linked list implementation */\nclass LinkedListQueue(\n    // Head node front, tail node rear\n    private var front: ListNode? = null,\n    private var rear: ListNode? = null,\n    private var queSize: Int = 0\n) {\n\n    /* Get the length of the queue */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check if the queue is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Enqueue */\n    fun push(num: Int) {\n        // Add num after the tail node\n        val node = ListNode(num)\n        // If the queue is empty, make both front and rear point to the node\n        if (front == null) {\n            front = node\n            rear = node\n            // If the queue is not empty, add the node after the tail node\n        } else {\n            rear?.next = node\n            rear = node\n        }\n        queSize++\n    }\n\n    /* Dequeue */\n    fun pop(): Int {\n        val num = peek()\n        // Delete head node\n        front = front?.next\n        queSize--\n        return num\n    }\n\n    /* Return list for printing */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return front!!._val\n    }\n\n    /* Convert linked list to Array and return */\n    fun toArray(): IntArray {\n        var node = front\n        val res = IntArray(size())\n        for (i in res.indices) {\n            res[i] = node!!._val\n            node = node.next\n        }\n        return res\n    }\n}\n
linkedlist_queue.rb
### Queue based on linked list ###\nclass LinkedListQueue\n  ### Get queue length ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @front = nil  # Head node front\n    @rear = nil   # Tail node rear\n    @size = 0\n  end\n\n  ### Check if queue is empty ###\n  def is_empty?\n    @front.nil?\n  end\n\n  ### Enqueue ###\n  def push(num)\n    # Add num after the tail node\n    node = ListNode.new(num)\n\n    # If queue is empty, set both front and rear to this node\n    if @front.nil?\n      @front = node\n      @rear = node\n    # If queue is not empty, add this node after rear\n    else\n      @rear.next = node\n      @rear = node\n    end\n\n    @size += 1\n  end\n\n  ### Dequeue ###\n  def pop\n    num = peek\n    # Delete head node\n    @front = @front.next\n    @size -= 1\n    num\n  end\n\n  ### Access front element ###\n  def peek\n    raise IndexError, 'Queue is empty' if is_empty?\n\n    @front.val\n  end\n\n  ### Convert linked list to Array and return ###\n  def to_array\n    queue = []\n    temp = @front\n    while temp\n      queue << temp.val\n      temp = temp.next\n    end\n    queue\n  end\nend\n
","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#2-array-implementation","level":3,"title":"2.   Array Implementation","text":"

Deleting the first element in an array has a time complexity of \\(O(n)\\), which would make the dequeue operation inefficient. However, we can use the following clever method to avoid this problem.

We can use a variable front to point to the index of the front element and maintain a variable size to record the queue length. We define rear = front + size, which calculates the position right after the rear element.

Based on this design, the valid interval containing elements in the array is [front, rear - 1]. The implementation methods for various operations are shown in Figure 5-6:

  • Enqueue operation: Assign the input element to the rear index and increase size by 1.
  • Dequeue operation: Simply increase front by 1 and decrease size by 1.

As you can see, both enqueue and dequeue operations require only one operation, with a time complexity of \\(O(1)\\).

ArrayQueuepush()pop()

Figure 5-6   Enqueue and dequeue operations in array implementation of queue

You may notice a problem: as we continuously enqueue and dequeue, both front and rear move to the right. When they reach the end of the array, they cannot continue moving. To solve this problem, we can treat the array as a \"circular array\" with head and tail connected.

For a circular array, we need to let front or rear wrap around to the beginning of the array when they cross the end. This periodic pattern can be implemented using the \"modulo operation,\" as shown in the code below:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_queue.py
class ArrayQueue:\n    \"\"\"Queue based on circular array implementation\"\"\"\n\n    def __init__(self, size: int):\n        \"\"\"Constructor\"\"\"\n        self._nums: list[int] = [0] * size  # Array for storing queue elements\n        self._front: int = 0  # Front pointer, points to the front of the queue element\n        self._size: int = 0  # Queue length\n\n    def capacity(self) -> int:\n        \"\"\"Get the capacity of the queue\"\"\"\n        return len(self._nums)\n\n    def size(self) -> int:\n        \"\"\"Get the length of the queue\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the queue is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int):\n        \"\"\"Enqueue\"\"\"\n        if self._size == self.capacity():\n            raise IndexError(\"Queue is full\")\n        # Calculate rear pointer, points to rear index + 1\n        # Use modulo operation to wrap rear around to the head after passing the tail of the array\n        rear: int = (self._front + self._size) % self.capacity()\n        # Add num to the rear of the queue\n        self._nums[rear] = num\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Dequeue\"\"\"\n        num: int = self.peek()\n        # Front pointer moves one position backward, if it passes the tail, return to the head of the array\n        self._front = (self._front + 1) % self.capacity()\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access front of the queue element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Queue is empty\")\n        return self._nums[self._front]\n\n    def to_list(self) -> list[int]:\n        \"\"\"Return list for printing\"\"\"\n        res = [0] * self.size()\n        j: int = self._front\n        for i in range(self.size()):\n            res[i] = self._nums[(j % self.capacity())]\n            j += 1\n        return res\n
array_queue.cpp
/* Queue based on circular array implementation */\nclass ArrayQueue {\n  private:\n    int *nums;       // Array for storing queue elements\n    int front;       // Front pointer, points to the front of the queue element\n    int queSize;     // Queue length\n    int queCapacity; // Queue capacity\n\n  public:\n    ArrayQueue(int capacity) {\n        // Initialize array\n        nums = new int[capacity];\n        queCapacity = capacity;\n        front = queSize = 0;\n    }\n\n    ~ArrayQueue() {\n        delete[] nums;\n    }\n\n    /* Get the capacity of the queue */\n    int capacity() {\n        return queCapacity;\n    }\n\n    /* Get the length of the queue */\n    int size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue */\n    void push(int num) {\n        if (queSize == queCapacity) {\n            cout << \"Queue is full\" << endl;\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        int rear = (front + queSize) % queCapacity;\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    int pop() {\n        int num = peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        front = (front + 1) % queCapacity;\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    int peek() {\n        if (isEmpty())\n            throw out_of_range(\"Queue is empty\");\n        return nums[front];\n    }\n\n    /* Convert array to Vector and return */\n    vector<int> toVector() {\n        // Elements enqueue\n        vector<int> arr(queSize);\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            arr[i] = nums[j % queCapacity];\n        }\n        return arr;\n    }\n};\n
array_queue.java
/* Queue based on circular array implementation */\nclass ArrayQueue {\n    private int[] nums; // Array for storing queue elements\n    private int front; // Front pointer, points to the front of the queue element\n    private int queSize; // Queue length\n\n    public ArrayQueue(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the capacity of the queue */\n    public int capacity() {\n        return nums.length;\n    }\n\n    /* Get the length of the queue */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    public boolean isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    public void push(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        int rear = (front + queSize) % capacity();\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int pop() {\n        int num = peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        front = (front + 1) % capacity();\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return nums[front];\n    }\n\n    /* Return array */\n    public int[] toArray() {\n        // Elements enqueue\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[j % capacity()];\n        }\n        return res;\n    }\n}\n
array_queue.cs
/* Queue based on circular array implementation */\nclass ArrayQueue {\n    int[] nums;  // Array for storing queue elements\n    int front;   // Front pointer, points to the front of the queue element\n    int queSize; // Queue length\n\n    public ArrayQueue(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the capacity of the queue */\n    int Capacity() {\n        return nums.Length;\n    }\n\n    /* Get the length of the queue */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check if the queue is empty */\n    public bool IsEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    public void Push(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        int rear = (front + queSize) % Capacity();\n        // Front pointer moves one position backward\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int Pop() {\n        int num = Peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        front = (front + 1) % Capacity();\n        queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return nums[front];\n    }\n\n    /* Return array */\n    public int[] ToArray() {\n        // Elements enqueue\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[j % this.Capacity()];\n        }\n        return res;\n    }\n}\n
array_queue.go
/* Queue based on circular array implementation */\ntype arrayQueue struct {\n    nums        []int // Array for storing queue elements\n    front       int   // Front pointer, points to the front of the queue element\n    queSize     int   // Queue length\n    queCapacity int   // Queue capacity (maximum number of elements)\n}\n\n/* Access front of the queue element */\nfunc newArrayQueue(queCapacity int) *arrayQueue {\n    return &arrayQueue{\n        nums:        make([]int, queCapacity),\n        queCapacity: queCapacity,\n        front:       0,\n        queSize:     0,\n    }\n}\n\n/* Get the length of the queue */\nfunc (q *arrayQueue) size() int {\n    return q.queSize\n}\n\n/* Check if the queue is empty */\nfunc (q *arrayQueue) isEmpty() bool {\n    return q.queSize == 0\n}\n\n/* Enqueue */\nfunc (q *arrayQueue) push(num int) {\n    // When rear == queCapacity, queue is full\n    if q.queSize == q.queCapacity {\n        return\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    // Add num to the rear of the queue\n    rear := (q.front + q.queSize) % q.queCapacity\n    // Front pointer moves one position backward\n    q.nums[rear] = num\n    q.queSize++\n}\n\n/* Dequeue */\nfunc (q *arrayQueue) pop() any {\n    num := q.peek()\n    if num == nil {\n        return nil\n    }\n\n    // Move front pointer backward by one position, if it passes the tail, return to array head\n    q.front = (q.front + 1) % q.queCapacity\n    q.queSize--\n    return num\n}\n\n/* Return list for printing */\nfunc (q *arrayQueue) peek() any {\n    if q.isEmpty() {\n        return nil\n    }\n    return q.nums[q.front]\n}\n\n/* Get Slice for printing */\nfunc (q *arrayQueue) toSlice() []int {\n    rear := (q.front + q.queSize)\n    if rear >= q.queCapacity {\n        rear %= q.queCapacity\n        return append(q.nums[q.front:], q.nums[:rear]...)\n    }\n    return q.nums[q.front:rear]\n}\n
array_queue.swift
/* Queue based on circular array implementation */\nclass ArrayQueue {\n    private var nums: [Int] // Array for storing queue elements\n    private var front: Int // Front pointer, points to the front of the queue element\n    private var _size: Int // Queue length\n\n    init(capacity: Int) {\n        // Initialize array\n        nums = Array(repeating: 0, count: capacity)\n        front = 0\n        _size = 0\n    }\n\n    /* Get the capacity of the queue */\n    func capacity() -> Int {\n        nums.count\n    }\n\n    /* Get the length of the queue */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check if the queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue */\n    func push(num: Int) {\n        if size() == capacity() {\n            print(\"Queue is full\")\n            return\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        let rear = (front + size()) % capacity()\n        // Front pointer moves one position backward\n        nums[rear] = num\n        _size += 1\n    }\n\n    /* Dequeue */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        front = (front + 1) % capacity()\n        _size -= 1\n        return num\n    }\n\n    /* Return list for printing */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Queue is empty\")\n        }\n        return nums[front]\n    }\n\n    /* Return array */\n    func toArray() -> [Int] {\n        // Elements enqueue\n        (front ..< front + size()).map { nums[$0 % capacity()] }\n    }\n}\n
array_queue.js
/* Queue based on circular array implementation */\nclass ArrayQueue {\n    #nums; // Array for storing queue elements\n    #front = 0; // Front pointer, points to the front of the queue element\n    #queSize = 0; // Queue length\n\n    constructor(capacity) {\n        this.#nums = new Array(capacity);\n    }\n\n    /* Get the capacity of the queue */\n    get capacity() {\n        return this.#nums.length;\n    }\n\n    /* Get the length of the queue */\n    get size() {\n        return this.#queSize;\n    }\n\n    /* Check if the queue is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Enqueue */\n    push(num) {\n        if (this.size === this.capacity) {\n            console.log('Queue is full');\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        const rear = (this.#front + this.size) % this.capacity;\n        // Front pointer moves one position backward\n        this.#nums[rear] = num;\n        this.#queSize++;\n    }\n\n    /* Dequeue */\n    pop() {\n        const num = this.peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        this.#front = (this.#front + 1) % this.capacity;\n        this.#queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek() {\n        if (this.isEmpty()) throw new Error('Queue is empty');\n        return this.#nums[this.#front];\n    }\n\n    /* Return Array */\n    toArray() {\n        // Elements enqueue\n        const arr = new Array(this.size);\n        for (let i = 0, j = this.#front; i < this.size; i++, j++) {\n            arr[i] = this.#nums[j % this.capacity];\n        }\n        return arr;\n    }\n}\n
array_queue.ts
/* Queue based on circular array implementation */\nclass ArrayQueue {\n    private nums: number[]; // Array for storing queue elements\n    private front: number; // Front pointer, points to the front of the queue element\n    private queSize: number; // Queue length\n\n    constructor(capacity: number) {\n        this.nums = new Array(capacity);\n        this.front = this.queSize = 0;\n    }\n\n    /* Get the capacity of the queue */\n    get capacity(): number {\n        return this.nums.length;\n    }\n\n    /* Get the length of the queue */\n    get size(): number {\n        return this.queSize;\n    }\n\n    /* Check if the queue is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Enqueue */\n    push(num: number): void {\n        if (this.size === this.capacity) {\n            console.log('Queue is full');\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        const rear = (this.front + this.queSize) % this.capacity;\n        // Front pointer moves one position backward\n        this.nums[rear] = num;\n        this.queSize++;\n    }\n\n    /* Dequeue */\n    pop(): number {\n        const num = this.peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        this.front = (this.front + 1) % this.capacity;\n        this.queSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek(): number {\n        if (this.isEmpty()) throw new Error('Queue is empty');\n        return this.nums[this.front];\n    }\n\n    /* Return Array */\n    toArray(): number[] {\n        // Elements enqueue\n        const arr = new Array(this.size);\n        for (let i = 0, j = this.front; i < this.size; i++, j++) {\n            arr[i] = this.nums[j % this.capacity];\n        }\n        return arr;\n    }\n}\n
array_queue.dart
/* Queue based on circular array implementation */\nclass ArrayQueue {\n  late List<int> _nums; // Array for storing queue elements\n  late int _front; // Front pointer, points to the front of the queue element\n  late int _queSize; // Queue length\n\n  ArrayQueue(int capacity) {\n    _nums = List.filled(capacity, 0);\n    _front = _queSize = 0;\n  }\n\n  /* Get the capacity of the queue */\n  int capaCity() {\n    return _nums.length;\n  }\n\n  /* Get the length of the queue */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check if the queue is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Enqueue */\n  void push(int _num) {\n    if (_queSize == capaCity()) {\n      throw Exception(\"Queue is full\");\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    // Add num to the rear of the queue\n    int rear = (_front + _queSize) % capaCity();\n    // Add _num to queue rear\n    _nums[rear] = _num;\n    _queSize++;\n  }\n\n  /* Dequeue */\n  int pop() {\n    int _num = peek();\n    // Move front pointer backward by one position, if it passes the tail, return to array head\n    _front = (_front + 1) % capaCity();\n    _queSize--;\n    return _num;\n  }\n\n  /* Return list for printing */\n  int peek() {\n    if (isEmpty()) {\n      throw Exception(\"Queue is empty\");\n    }\n    return _nums[_front];\n  }\n\n  /* Return Array */\n  List<int> toArray() {\n    // Elements enqueue\n    final List<int> res = List.filled(_queSize, 0);\n    for (int i = 0, j = _front; i < _queSize; i++, j++) {\n      res[i] = _nums[j % capaCity()];\n    }\n    return res;\n  }\n}\n
array_queue.rs
/* Queue based on circular array implementation */\nstruct ArrayQueue<T> {\n    nums: Vec<T>,      // Array for storing queue elements\n    front: i32,        // Front pointer, points to the front of the queue element\n    que_size: i32,     // Queue length\n    que_capacity: i32, // Queue capacity\n}\n\nimpl<T: Copy + Default> ArrayQueue<T> {\n    /* Constructor */\n    fn new(capacity: i32) -> ArrayQueue<T> {\n        ArrayQueue {\n            nums: vec![T::default(); capacity as usize],\n            front: 0,\n            que_size: 0,\n            que_capacity: capacity,\n        }\n    }\n\n    /* Get the capacity of the queue */\n    fn capacity(&self) -> i32 {\n        self.que_capacity\n    }\n\n    /* Get the length of the queue */\n    fn size(&self) -> i32 {\n        self.que_size\n    }\n\n    /* Check if the queue is empty */\n    fn is_empty(&self) -> bool {\n        self.que_size == 0\n    }\n\n    /* Enqueue */\n    fn push(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Queue is full\");\n            return;\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        let rear = (self.front + self.que_size) % self.que_capacity;\n        // Front pointer moves one position backward\n        self.nums[rear as usize] = num;\n        self.que_size += 1;\n    }\n\n    /* Dequeue */\n    fn pop(&mut self) -> T {\n        let num = self.peek();\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        self.front = (self.front + 1) % self.que_capacity;\n        self.que_size -= 1;\n        num\n    }\n\n    /* Return list for printing */\n    fn peek(&self) -> T {\n        if self.is_empty() {\n            panic!(\"index out of bounds\");\n        }\n        self.nums[self.front as usize]\n    }\n\n    /* Return array */\n    fn to_vector(&self) -> Vec<T> {\n        let cap = self.que_capacity;\n        let mut j = self.front;\n        let mut arr = vec![T::default(); cap as usize];\n        for i in 0..self.que_size {\n            arr[i as usize] = self.nums[(j % cap) as usize];\n            j += 1;\n        }\n        arr\n    }\n}\n
array_queue.c
/* Queue based on circular array implementation */\ntypedef struct {\n    int *nums;       // Array for storing queue elements\n    int front;       // Front pointer, points to the front of the queue element\n    int queSize;     // Current number of elements in the queue\n    int queCapacity; // Queue capacity\n} ArrayQueue;\n\n/* Constructor */\nArrayQueue *newArrayQueue(int capacity) {\n    ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue));\n    // Initialize array\n    queue->queCapacity = capacity;\n    queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity);\n    queue->front = queue->queSize = 0;\n    return queue;\n}\n\n/* Destructor */\nvoid delArrayQueue(ArrayQueue *queue) {\n    free(queue->nums);\n    free(queue);\n}\n\n/* Get the capacity of the queue */\nint capacity(ArrayQueue *queue) {\n    return queue->queCapacity;\n}\n\n/* Get the length of the queue */\nint size(ArrayQueue *queue) {\n    return queue->queSize;\n}\n\n/* Check if the queue is empty */\nbool empty(ArrayQueue *queue) {\n    return queue->queSize == 0;\n}\n\n/* Return list for printing */\nint peek(ArrayQueue *queue) {\n    assert(size(queue) != 0);\n    return queue->nums[queue->front];\n}\n\n/* Enqueue */\nvoid push(ArrayQueue *queue, int num) {\n    if (size(queue) == capacity(queue)) {\n        printf(\"Queue is full\\r\\n\");\n        return;\n    }\n    // Use modulo operation to wrap rear around to the head after passing the tail of the array\n    // Add num to the rear of the queue\n    int rear = (queue->front + queue->queSize) % queue->queCapacity;\n    // Front pointer moves one position backward\n    queue->nums[rear] = num;\n    queue->queSize++;\n}\n\n/* Dequeue */\nint pop(ArrayQueue *queue) {\n    int num = peek(queue);\n    // Move front pointer backward by one position, if it passes the tail, return to array head\n    queue->front = (queue->front + 1) % queue->queCapacity;\n    queue->queSize--;\n    return num;\n}\n\n/* Return array for printing */\nint *toArray(ArrayQueue *queue, int *queSize) {\n    *queSize = queue->queSize;\n    int *res = (int *)calloc(queue->queSize, sizeof(int));\n    int j = queue->front;\n    for (int i = 0; i < queue->queSize; i++) {\n        res[i] = queue->nums[j % queue->queCapacity];\n        j++;\n    }\n    return res;\n}\n
array_queue.kt
/* Queue based on circular array implementation */\nclass ArrayQueue(capacity: Int) {\n    private val nums: IntArray = IntArray(capacity) // Array for storing queue elements\n    private var front: Int = 0 // Front pointer, points to the front of the queue element\n    private var queSize: Int = 0 // Queue length\n\n    /* Get the capacity of the queue */\n    fun capacity(): Int {\n        return nums.size\n    }\n\n    /* Get the length of the queue */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check if the queue is empty */\n    fun isEmpty(): Boolean {\n        return queSize == 0\n    }\n\n    /* Enqueue */\n    fun push(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Queue is full\")\n            return\n        }\n        // Use modulo operation to wrap rear around to the head after passing the tail of the array\n        // Add num to the rear of the queue\n        val rear = (front + queSize) % capacity()\n        // Front pointer moves one position backward\n        nums[rear] = num\n        queSize++\n    }\n\n    /* Dequeue */\n    fun pop(): Int {\n        val num = peek()\n        // Move front pointer backward by one position, if it passes the tail, return to array head\n        front = (front + 1) % capacity()\n        queSize--\n        return num\n    }\n\n    /* Return list for printing */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return nums[front]\n    }\n\n    /* Return array */\n    fun toArray(): IntArray {\n        // Elements enqueue\n        val res = IntArray(queSize)\n        var i = 0\n        var j = front\n        while (i < queSize) {\n            res[i] = nums[j % capacity()]\n            i++\n            j++\n        }\n        return res\n    }\n}\n
array_queue.rb
### Queue based on circular array ###\nclass ArrayQueue\n  ### Get queue length ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize(size)\n    @nums = Array.new(size, 0) # Array for storing queue elements\n    @front = 0 # Front pointer, points to the front of the queue element\n    @size = 0 # Queue length\n  end\n\n  ### Get queue capacity ###\n  def capacity\n    @nums.length\n  end\n\n  ### Check if queue is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue ###\n  def push(num)\n    raise IndexError, 'Queue is full' if size == capacity\n\n    # Use modulo operation to wrap rear around to the head after passing the tail of the array\n    # Add num to the rear of the queue\n    rear = (@front + size) % capacity\n    # Front pointer moves one position backward\n    @nums[rear] = num\n    @size += 1\n  end\n\n  ### Dequeue ###\n  def pop\n    num = peek\n    # Move front pointer backward by one position, if it passes the tail, return to array head\n    @front = (@front + 1) % capacity\n    @size -= 1\n    num\n  end\n\n  ### Access front element ###\n  def peek\n    raise IndexError, 'Queue is empty' if is_empty?\n\n    @nums[@front]\n  end\n\n  ### Return list for printing ###\n  def to_array\n    res = Array.new(size, 0)\n    j = @front\n\n    for i in 0...size\n      res[i] = @nums[j % capacity]\n      j += 1\n    end\n\n    res\n  end\nend\n

The queue implemented above still has limitations: its length is immutable. However, this problem is not difficult to solve. We can replace the array with a dynamic array to introduce an expansion mechanism. Interested readers can try to implement this themselves.

The comparison conclusions for the two implementations are consistent with those for stacks and will not be repeated here.

","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#523-typical-applications-of-queue","level":2,"title":"5.2.3   Typical Applications of Queue","text":"
  • Taobao orders. After shoppers place orders, the orders are added to a queue, and the system subsequently processes the orders in the queue according to their sequence. During Double Eleven, massive orders are generated in a short time, and high concurrency becomes a key challenge that engineers need to tackle.
  • Various to-do tasks. Any scenario that needs to implement \"first come, first served\" functionality, such as a printer's task queue or a restaurant's order queue, can effectively maintain the processing order using queues.
","path":["Chapter 5. Stack and Queue","5.2   Queue"],"tags":[]},{"location":"chapter_stack_and_queue/stack/","level":1,"title":"5.1   Stack","text":"

A stack is a linear data structure that follows the Last In First Out (LIFO) logic.

We can compare a stack to a pile of plates on a table. If we specify that only one plate can be moved at a time, then to get the bottom plate, we must first remove the plates above it one by one. If we replace the plates with various types of elements (such as integers, characters, objects, etc.), we get the stack data structure.

As shown in Figure 5-1, we call the top of the stacked elements the \"top\" and the bottom the \"base.\" The operation of adding an element to the top is called \"push,\" and the operation of removing the top element is called \"pop.\"

Figure 5-1   LIFO rule of stack

","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#511-common-stack-operations","level":2,"title":"5.1.1   Common Stack Operations","text":"

The common operations on a stack are shown in Table 5-1. The specific method names depend on the programming language used. Here, we use the common naming convention of push(), pop(), and peek().

Table 5-1   Efficiency of Stack Operations

Method Description Time Complexity push() Push element onto stack (add to top) \\(O(1)\\) pop() Pop top element from stack \\(O(1)\\) peek() Access top element \\(O(1)\\)

Typically, we can directly use the built-in stack class provided by the programming language. However, some languages may not provide a dedicated stack class. In these cases, we can use the language's \"array\" or \"linked list\" as a stack and ignore operations unrelated to the stack in the program logic.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby stack.py
# Initialize stack\n# Python does not have a built-in stack class, can use list as a stack\nstack: list[int] = []\n\n# Push elements\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n\n# Access top element\npeek: int = stack[-1]\n\n# Pop element\npop: int = stack.pop()\n\n# Get stack length\nsize: int = len(stack)\n\n# Check if empty\nis_empty: bool = len(stack) == 0\n
stack.cpp
/* Initialize stack */\nstack<int> stack;\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access top element */\nint top = stack.top();\n\n/* Pop element */\nstack.pop(); // No return value\n\n/* Get stack length */\nint size = stack.size();\n\n/* Check if empty */\nbool empty = stack.empty();\n
stack.java
/* Initialize stack */\nStack<Integer> stack = new Stack<>();\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access top element */\nint peek = stack.peek();\n\n/* Pop element */\nint pop = stack.pop();\n\n/* Get stack length */\nint size = stack.size();\n\n/* Check if empty */\nboolean isEmpty = stack.isEmpty();\n
stack.cs
/* Initialize stack */\nStack<int> stack = new();\n\n/* Push elements */\nstack.Push(1);\nstack.Push(3);\nstack.Push(2);\nstack.Push(5);\nstack.Push(4);\n\n/* Access top element */\nint peek = stack.Peek();\n\n/* Pop element */\nint pop = stack.Pop();\n\n/* Get stack length */\nint size = stack.Count;\n\n/* Check if empty */\nbool isEmpty = stack.Count == 0;\n
stack_test.go
/* Initialize stack */\n// In Go, it is recommended to use Slice as a stack\nvar stack []int\n\n/* Push elements */\nstack = append(stack, 1)\nstack = append(stack, 3)\nstack = append(stack, 2)\nstack = append(stack, 5)\nstack = append(stack, 4)\n\n/* Access top element */\npeek := stack[len(stack)-1]\n\n/* Pop element */\npop := stack[len(stack)-1]\nstack = stack[:len(stack)-1]\n\n/* Get stack length */\nsize := len(stack)\n\n/* Check if empty */\nisEmpty := len(stack) == 0\n
stack.swift
/* Initialize stack */\n// Swift does not have a built-in stack class, can use Array as a stack\nvar stack: [Int] = []\n\n/* Push elements */\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n\n/* Access top element */\nlet peek = stack.last!\n\n/* Pop element */\nlet pop = stack.removeLast()\n\n/* Get stack length */\nlet size = stack.count\n\n/* Check if empty */\nlet isEmpty = stack.isEmpty\n
stack.js
/* Initialize stack */\n// JavaScript does not have a built-in stack class, can use Array as a stack\nconst stack = [];\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access top element */\nconst peek = stack[stack.length-1];\n\n/* Pop element */\nconst pop = stack.pop();\n\n/* Get stack length */\nconst size = stack.length;\n\n/* Check if empty */\nconst is_empty = stack.length === 0;\n
stack.ts
/* Initialize stack */\n// TypeScript does not have a built-in stack class, can use Array as a stack\nconst stack: number[] = [];\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access top element */\nconst peek = stack[stack.length - 1];\n\n/* Pop element */\nconst pop = stack.pop();\n\n/* Get stack length */\nconst size = stack.length;\n\n/* Check if empty */\nconst is_empty = stack.length === 0;\n
stack.dart
/* Initialize stack */\n// Dart does not have a built-in stack class, can use List as a stack\nList<int> stack = [];\n\n/* Push elements */\nstack.add(1);\nstack.add(3);\nstack.add(2);\nstack.add(5);\nstack.add(4);\n\n/* Access top element */\nint peek = stack.last;\n\n/* Pop element */\nint pop = stack.removeLast();\n\n/* Get stack length */\nint size = stack.length;\n\n/* Check if empty */\nbool isEmpty = stack.isEmpty;\n
stack.rs
/* Initialize stack */\n// Use Vec as a stack\nlet mut stack: Vec<i32> = Vec::new();\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access top element */\nlet top = stack.last().unwrap();\n\n/* Pop element */\nlet pop = stack.pop().unwrap();\n\n/* Get stack length */\nlet size = stack.len();\n\n/* Check if empty */\nlet is_empty = stack.is_empty();\n
stack.c
// C does not provide a built-in stack\n
stack.kt
/* Initialize stack */\nval stack = Stack<Int>()\n\n/* Push elements */\nstack.push(1)\nstack.push(3)\nstack.push(2)\nstack.push(5)\nstack.push(4)\n\n/* Access top element */\nval peek = stack.peek()\n\n/* Pop element */\nval pop = stack.pop()\n\n/* Get stack length */\nval size = stack.size\n\n/* Check if empty */\nval isEmpty = stack.isEmpty()\n
stack.rb
# Initialize stack\n# Ruby does not have a built-in stack class, can use Array as a stack\nstack = []\n\n# Push elements\nstack << 1\nstack << 3\nstack << 2\nstack << 5\nstack << 4\n\n# Access top element\npeek = stack.last\n\n# Pop element\npop = stack.pop\n\n# Get stack length\nsize = stack.length\n\n# Check if empty\nis_empty = stack.empty?\n
Code Visualization

Full Screen >

","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#512-stack-implementation","level":2,"title":"5.1.2   Stack Implementation","text":"

To gain a deeper understanding of how a stack operates, let's try implementing a stack class ourselves.

A stack follows the LIFO principle, so we can only add or remove elements at the top. However, both arrays and linked lists allow adding and removing elements at any position. Therefore, a stack can be viewed as a restricted array or linked list. In other words, we can \"shield\" some irrelevant operations of arrays or linked lists so that their external logic conforms to the characteristics of a stack.

","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#1-linked-list-implementation","level":3,"title":"1.   Linked List Implementation","text":"

When implementing a stack using a linked list, we can treat the head node of the linked list as the top of the stack and the tail node as the base.

As shown in Figure 5-2, for the push operation, we simply insert an element at the head of the linked list. This node insertion method is called the \"head insertion method.\" For the pop operation, we just need to remove the head node from the linked list.

LinkedListStackpush()pop()

Figure 5-2   Push and pop operations in linked list implementation of stack

Below is sample code for implementing a stack based on a linked list:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_stack.py
class LinkedListStack:\n    \"\"\"Stack based on linked list implementation\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._peek: ListNode | None = None\n        self._size: int = 0\n\n    def size(self) -> int:\n        \"\"\"Get the length of the stack\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the stack is empty\"\"\"\n        return self._size == 0\n\n    def push(self, val: int):\n        \"\"\"Push\"\"\"\n        node = ListNode(val)\n        node.next = self._peek\n        self._peek = node\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Pop\"\"\"\n        num = self.peek()\n        self._peek = self._peek.next\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access top of the stack element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._peek.val\n\n    def to_list(self) -> list[int]:\n        \"\"\"Convert to list for printing\"\"\"\n        arr = []\n        node = self._peek\n        while node:\n            arr.append(node.val)\n            node = node.next\n        arr.reverse()\n        return arr\n
linkedlist_stack.cpp
/* Stack based on linked list implementation */\nclass LinkedListStack {\n  private:\n    ListNode *stackTop; // Use head node as stack top\n    int stkSize;        // Stack length\n\n  public:\n    LinkedListStack() {\n        stackTop = nullptr;\n        stkSize = 0;\n    }\n\n    ~LinkedListStack() {\n        // Traverse linked list to delete nodes and free memory\n        freeMemoryLinkedList(stackTop);\n    }\n\n    /* Get the length of the stack */\n    int size() {\n        return stkSize;\n    }\n\n    /* Check if the stack is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    void push(int num) {\n        ListNode *node = new ListNode(num);\n        node->next = stackTop;\n        stackTop = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    int pop() {\n        int num = top();\n        ListNode *tmp = stackTop;\n        stackTop = stackTop->next;\n        // Free memory\n        delete tmp;\n        stkSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    int top() {\n        if (isEmpty())\n            throw out_of_range(\"Stack is empty\");\n        return stackTop->val;\n    }\n\n    /* Convert List to Array and return */\n    vector<int> toVector() {\n        ListNode *node = stackTop;\n        vector<int> res(size());\n        for (int i = res.size() - 1; i >= 0; i--) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
linkedlist_stack.java
/* Stack based on linked list implementation */\nclass LinkedListStack {\n    private ListNode stackPeek; // Use head node as stack top\n    private int stkSize = 0; // Stack length\n\n    public LinkedListStack() {\n        stackPeek = null;\n    }\n\n    /* Get the length of the stack */\n    public int size() {\n        return stkSize;\n    }\n\n    /* Check if the stack is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    public void push(int num) {\n        ListNode node = new ListNode(num);\n        node.next = stackPeek;\n        stackPeek = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    public int pop() {\n        int num = peek();\n        stackPeek = stackPeek.next;\n        stkSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stackPeek.val;\n    }\n\n    /* Convert List to Array and return */\n    public int[] toArray() {\n        ListNode node = stackPeek;\n        int[] res = new int[size()];\n        for (int i = res.length - 1; i >= 0; i--) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_stack.cs
/* Stack based on linked list implementation */\nclass LinkedListStack {\n    ListNode? stackPeek;  // Use head node as stack top\n    int stkSize = 0;   // Stack length\n\n    public LinkedListStack() {\n        stackPeek = null;\n    }\n\n    /* Get the length of the stack */\n    public int Size() {\n        return stkSize;\n    }\n\n    /* Check if the stack is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Push */\n    public void Push(int num) {\n        ListNode node = new(num) {\n            next = stackPeek\n        };\n        stackPeek = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    public int Pop() {\n        int num = Peek();\n        stackPeek = stackPeek!.next;\n        stkSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return stackPeek!.val;\n    }\n\n    /* Convert List to Array and return */\n    public int[] ToArray() {\n        if (stackPeek == null)\n            return [];\n\n        ListNode? node = stackPeek;\n        int[] res = new int[Size()];\n        for (int i = res.Length - 1; i >= 0; i--) {\n            res[i] = node!.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_stack.go
/* Stack based on linked list implementation */\ntype linkedListStack struct {\n    // Use built-in package list to implement stack\n    data *list.List\n}\n\n/* Access top of the stack element */\nfunc newLinkedListStack() *linkedListStack {\n    return &linkedListStack{\n        data: list.New(),\n    }\n}\n\n/* Push */\nfunc (s *linkedListStack) push(value int) {\n    s.data.PushBack(value)\n}\n\n/* Pop */\nfunc (s *linkedListStack) pop() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Return list for printing */\nfunc (s *linkedListStack) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    return e.Value\n}\n\n/* Get the length of the stack */\nfunc (s *linkedListStack) size() int {\n    return s.data.Len()\n}\n\n/* Check if the stack is empty */\nfunc (s *linkedListStack) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get List for printing */\nfunc (s *linkedListStack) toList() *list.List {\n    return s.data\n}\n
linkedlist_stack.swift
/* Stack based on linked list implementation */\nclass LinkedListStack {\n    private var _peek: ListNode? // Use head node as stack top\n    private var _size: Int // Stack length\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the length of the stack */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check if the stack is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Push */\n    func push(num: Int) {\n        let node = ListNode(x: num)\n        node.next = _peek\n        _peek = node\n        _size += 1\n    }\n\n    /* Pop */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        _peek = _peek?.next\n        _size -= 1\n        return num\n    }\n\n    /* Return list for printing */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return _peek!.val\n    }\n\n    /* Convert List to Array and return */\n    func toArray() -> [Int] {\n        var node = _peek\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices.reversed() {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
linkedlist_stack.js
/* Stack based on linked list implementation */\nclass LinkedListStack {\n    #stackPeek; // Use head node as stack top\n    #stkSize = 0; // Stack length\n\n    constructor() {\n        this.#stackPeek = null;\n    }\n\n    /* Get the length of the stack */\n    get size() {\n        return this.#stkSize;\n    }\n\n    /* Check if the stack is empty */\n    isEmpty() {\n        return this.size === 0;\n    }\n\n    /* Push */\n    push(num) {\n        const node = new ListNode(num);\n        node.next = this.#stackPeek;\n        this.#stackPeek = node;\n        this.#stkSize++;\n    }\n\n    /* Pop */\n    pop() {\n        const num = this.peek();\n        this.#stackPeek = this.#stackPeek.next;\n        this.#stkSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek() {\n        if (!this.#stackPeek) throw new Error('Stack is empty');\n        return this.#stackPeek.val;\n    }\n\n    /* Convert linked list to Array and return */\n    toArray() {\n        let node = this.#stackPeek;\n        const res = new Array(this.size);\n        for (let i = res.length - 1; i >= 0; i--) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
linkedlist_stack.ts
/* Stack based on linked list implementation */\nclass LinkedListStack {\n    private stackPeek: ListNode | null; // Use head node as stack top\n    private stkSize: number = 0; // Stack length\n\n    constructor() {\n        this.stackPeek = null;\n    }\n\n    /* Get the length of the stack */\n    get size(): number {\n        return this.stkSize;\n    }\n\n    /* Check if the stack is empty */\n    isEmpty(): boolean {\n        return this.size === 0;\n    }\n\n    /* Push */\n    push(num: number): void {\n        const node = new ListNode(num);\n        node.next = this.stackPeek;\n        this.stackPeek = node;\n        this.stkSize++;\n    }\n\n    /* Pop */\n    pop(): number {\n        const num = this.peek();\n        if (!this.stackPeek) throw new Error('Stack is empty');\n        this.stackPeek = this.stackPeek.next;\n        this.stkSize--;\n        return num;\n    }\n\n    /* Return list for printing */\n    peek(): number {\n        if (!this.stackPeek) throw new Error('Stack is empty');\n        return this.stackPeek.val;\n    }\n\n    /* Convert linked list to Array and return */\n    toArray(): number[] {\n        let node = this.stackPeek;\n        const res = new Array<number>(this.size);\n        for (let i = res.length - 1; i >= 0; i--) {\n            res[i] = node!.val;\n            node = node!.next;\n        }\n        return res;\n    }\n}\n
linkedlist_stack.dart
/* Stack implemented based on linked list class */\nclass LinkedListStack {\n  ListNode? _stackPeek; // Use head node as stack top\n  int _stkSize = 0; // Stack length\n\n  LinkedListStack() {\n    _stackPeek = null;\n  }\n\n  /* Get the length of the stack */\n  int size() {\n    return _stkSize;\n  }\n\n  /* Check if the stack is empty */\n  bool isEmpty() {\n    return _stkSize == 0;\n  }\n\n  /* Push */\n  void push(int _num) {\n    final ListNode node = ListNode(_num);\n    node.next = _stackPeek;\n    _stackPeek = node;\n    _stkSize++;\n  }\n\n  /* Pop */\n  int pop() {\n    final int _num = peek();\n    _stackPeek = _stackPeek!.next;\n    _stkSize--;\n    return _num;\n  }\n\n  /* Return list for printing */\n  int peek() {\n    if (_stackPeek == null) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stackPeek!.val;\n  }\n\n  /* Convert linked list to List and return */\n  List<int> toList() {\n    ListNode? node = _stackPeek;\n    List<int> list = [];\n    while (node != null) {\n      list.add(node.val);\n      node = node.next;\n    }\n    list = list.reversed.toList();\n    return list;\n  }\n}\n
linkedlist_stack.rs
/* Stack based on linked list implementation */\n#[allow(dead_code)]\npub struct LinkedListStack<T> {\n    stack_peek: Option<Rc<RefCell<ListNode<T>>>>, // Use head node as stack top\n    stk_size: usize,                              // Stack length\n}\n\nimpl<T: Copy> LinkedListStack<T> {\n    pub fn new() -> Self {\n        Self {\n            stack_peek: None,\n            stk_size: 0,\n        }\n    }\n\n    /* Get the length of the stack */\n    pub fn size(&self) -> usize {\n        return self.stk_size;\n    }\n\n    /* Check if the stack is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.size() == 0;\n    }\n\n    /* Push */\n    pub fn push(&mut self, num: T) {\n        let node = ListNode::new(num);\n        node.borrow_mut().next = self.stack_peek.take();\n        self.stack_peek = Some(node);\n        self.stk_size += 1;\n    }\n\n    /* Pop */\n    pub fn pop(&mut self) -> Option<T> {\n        self.stack_peek.take().map(|old_head| {\n            self.stack_peek = old_head.borrow_mut().next.take();\n            self.stk_size -= 1;\n\n            old_head.borrow().val\n        })\n    }\n\n    /* Return list for printing */\n    pub fn peek(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.stack_peek.as_ref()\n    }\n\n    /* Convert List to Array and return */\n    pub fn to_array(&self) -> Vec<T> {\n        fn _to_array<T: Sized + Copy>(head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n            if let Some(node) = head {\n                let mut nums = _to_array(node.borrow().next.as_ref());\n                nums.push(node.borrow().val);\n                return nums;\n            }\n            return Vec::new();\n        }\n\n        _to_array(self.peek())\n    }\n}\n
linkedlist_stack.c
/* Stack based on linked list implementation */\ntypedef struct {\n    ListNode *top; // Use head node as stack top\n    int size;      // Stack length\n} LinkedListStack;\n\n/* Constructor */\nLinkedListStack *newLinkedListStack() {\n    LinkedListStack *s = malloc(sizeof(LinkedListStack));\n    s->top = NULL;\n    s->size = 0;\n    return s;\n}\n\n/* Destructor */\nvoid delLinkedListStack(LinkedListStack *s) {\n    while (s->top) {\n        ListNode *n = s->top->next;\n        free(s->top);\n        s->top = n;\n    }\n    free(s);\n}\n\n/* Get the length of the stack */\nint size(LinkedListStack *s) {\n    return s->size;\n}\n\n/* Check if the stack is empty */\nbool isEmpty(LinkedListStack *s) {\n    return size(s) == 0;\n}\n\n/* Push */\nvoid push(LinkedListStack *s, int num) {\n    ListNode *node = (ListNode *)malloc(sizeof(ListNode));\n    node->next = s->top; // Update new node's pointer field\n    node->val = num;     // Update new node's data field\n    s->top = node;       // Update stack top\n    s->size++;           // Update stack size\n}\n\n/* Return list for printing */\nint peek(LinkedListStack *s) {\n    if (s->size == 0) {\n        printf(\"Stack is empty\\n\");\n        return INT_MAX;\n    }\n    return s->top->val;\n}\n\n/* Pop */\nint pop(LinkedListStack *s) {\n    int val = peek(s);\n    ListNode *tmp = s->top;\n    s->top = s->top->next;\n    // Free memory\n    free(tmp);\n    s->size--;\n    return val;\n}\n
linkedlist_stack.kt
/* Stack based on linked list implementation */\nclass LinkedListStack(\n    private var stackPeek: ListNode? = null, // Use head node as stack top\n    private var stkSize: Int = 0 // Stack length\n) {\n\n    /* Get the length of the stack */\n    fun size(): Int {\n        return stkSize\n    }\n\n    /* Check if the stack is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Push */\n    fun push(num: Int) {\n        val node = ListNode(num)\n        node.next = stackPeek\n        stackPeek = node\n        stkSize++\n    }\n\n    /* Pop */\n    fun pop(): Int? {\n        val num = peek()\n        stackPeek = stackPeek?.next\n        stkSize--\n        return num\n    }\n\n    /* Return list for printing */\n    fun peek(): Int? {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stackPeek?._val\n    }\n\n    /* Convert List to Array and return */\n    fun toArray(): IntArray {\n        var node = stackPeek\n        val res = IntArray(size())\n        for (i in res.size - 1 downTo 0) {\n            res[i] = node?._val!!\n            node = node.next\n        }\n        return res\n    }\n}\n
linkedlist_stack.rb
### Stack based on linked list ###\nclass LinkedListStack\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @size = 0\n  end\n\n  ### Check if stack is empty ###\n  def is_empty?\n    @peek.nil?\n  end\n\n  ### Push ###\n  def push(val)\n    node = ListNode.new(val)\n    node.next = @peek\n    @peek = node\n    @size += 1\n  end\n\n  ### Pop ###\n  def pop\n    num = peek\n    @peek = @peek.next\n    @size -= 1\n    num\n  end\n\n  ### Access top element ###\n  def peek\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @peek.val\n  end\n\n  ### Convert linked list to Array and return ###\n  def to_array\n    arr = []\n    node = @peek\n    while node\n      arr << node.val\n      node = node.next\n    end\n    arr.reverse\n  end\nend\n
","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#2-array-implementation","level":3,"title":"2.   Array Implementation","text":"

When implementing a stack using an array, we can treat the end of the array as the top of the stack. As shown in Figure 5-3, push and pop operations correspond to adding and removing elements at the end of the array, both with a time complexity of \\(O(1)\\).

ArrayStackpush()pop()

Figure 5-3   Push and pop operations in array implementation of stack

Since elements pushed onto the stack may increase continuously, we can use a dynamic array, which eliminates the need to handle array expansion ourselves. Here is the sample code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_stack.py
class ArrayStack:\n    \"\"\"Stack based on array implementation\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._stack: list[int] = []\n\n    def size(self) -> int:\n        \"\"\"Get the length of the stack\"\"\"\n        return len(self._stack)\n\n    def is_empty(self) -> bool:\n        \"\"\"Check if the stack is empty\"\"\"\n        return self.size() == 0\n\n    def push(self, item: int):\n        \"\"\"Push\"\"\"\n        self._stack.append(item)\n\n    def pop(self) -> int:\n        \"\"\"Pop\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._stack.pop()\n\n    def peek(self) -> int:\n        \"\"\"Access top of the stack element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._stack[-1]\n\n    def to_list(self) -> list[int]:\n        \"\"\"Return list for printing\"\"\"\n        return self._stack\n
array_stack.cpp
/* Stack based on array implementation */\nclass ArrayStack {\n  private:\n    vector<int> stack;\n\n  public:\n    /* Get the length of the stack */\n    int size() {\n        return stack.size();\n    }\n\n    /* Check if the stack is empty */\n    bool isEmpty() {\n        return stack.size() == 0;\n    }\n\n    /* Push */\n    void push(int num) {\n        stack.push_back(num);\n    }\n\n    /* Pop */\n    int pop() {\n        int num = top();\n        stack.pop_back();\n        return num;\n    }\n\n    /* Return list for printing */\n    int top() {\n        if (isEmpty())\n            throw out_of_range(\"Stack is empty\");\n        return stack.back();\n    }\n\n    /* Return Vector */\n    vector<int> toVector() {\n        return stack;\n    }\n};\n
array_stack.java
/* Stack based on array implementation */\nclass ArrayStack {\n    private ArrayList<Integer> stack;\n\n    public ArrayStack() {\n        // Initialize list (dynamic array)\n        stack = new ArrayList<>();\n    }\n\n    /* Get the length of the stack */\n    public int size() {\n        return stack.size();\n    }\n\n    /* Check if the stack is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    public void push(int num) {\n        stack.add(num);\n    }\n\n    /* Pop */\n    public int pop() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stack.remove(size() - 1);\n    }\n\n    /* Return list for printing */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stack.get(size() - 1);\n    }\n\n    /* Convert List to Array and return */\n    public Object[] toArray() {\n        return stack.toArray();\n    }\n}\n
array_stack.cs
/* Stack based on array implementation */\nclass ArrayStack {\n    List<int> stack;\n    public ArrayStack() {\n        // Initialize list (dynamic array)\n        stack = [];\n    }\n\n    /* Get the length of the stack */\n    public int Size() {\n        return stack.Count;\n    }\n\n    /* Check if the stack is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Push */\n    public void Push(int num) {\n        stack.Add(num);\n    }\n\n    /* Pop */\n    public int Pop() {\n        if (IsEmpty())\n            throw new Exception();\n        var val = Peek();\n        stack.RemoveAt(Size() - 1);\n        return val;\n    }\n\n    /* Return list for printing */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return stack[Size() - 1];\n    }\n\n    /* Convert List to Array and return */\n    public int[] ToArray() {\n        return [.. stack];\n    }\n}\n
array_stack.go
/* Stack based on array implementation */\ntype arrayStack struct {\n    data []int // Data\n}\n\n/* Access top of the stack element */\nfunc newArrayStack() *arrayStack {\n    return &arrayStack{\n        // Set stack length to 0, capacity to 16\n        data: make([]int, 0, 16),\n    }\n}\n\n/* Stack length */\nfunc (s *arrayStack) size() int {\n    return len(s.data)\n}\n\n/* Is stack empty */\nfunc (s *arrayStack) isEmpty() bool {\n    return s.size() == 0\n}\n\n/* Push */\nfunc (s *arrayStack) push(v int) {\n    // Slice will automatically expand\n    s.data = append(s.data, v)\n}\n\n/* Pop */\nfunc (s *arrayStack) pop() any {\n    val := s.peek()\n    s.data = s.data[:len(s.data)-1]\n    return val\n}\n\n/* Get stack top element */\nfunc (s *arrayStack) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    val := s.data[len(s.data)-1]\n    return val\n}\n\n/* Get Slice for printing */\nfunc (s *arrayStack) toSlice() []int {\n    return s.data\n}\n
array_stack.swift
/* Stack based on array implementation */\nclass ArrayStack {\n    private var stack: [Int]\n\n    init() {\n        // Initialize list (dynamic array)\n        stack = []\n    }\n\n    /* Get the length of the stack */\n    func size() -> Int {\n        stack.count\n    }\n\n    /* Check if the stack is empty */\n    func isEmpty() -> Bool {\n        stack.isEmpty\n    }\n\n    /* Push */\n    func push(num: Int) {\n        stack.append(num)\n    }\n\n    /* Pop */\n    @discardableResult\n    func pop() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return stack.removeLast()\n    }\n\n    /* Return list for printing */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return stack.last!\n    }\n\n    /* Convert List to Array and return */\n    func toArray() -> [Int] {\n        stack\n    }\n}\n
array_stack.js
/* Stack based on array implementation */\nclass ArrayStack {\n    #stack;\n    constructor() {\n        this.#stack = [];\n    }\n\n    /* Get the length of the stack */\n    get size() {\n        return this.#stack.length;\n    }\n\n    /* Check if the stack is empty */\n    isEmpty() {\n        return this.#stack.length === 0;\n    }\n\n    /* Push */\n    push(num) {\n        this.#stack.push(num);\n    }\n\n    /* Pop */\n    pop() {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.#stack.pop();\n    }\n\n    /* Return list for printing */\n    top() {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.#stack[this.#stack.length - 1];\n    }\n\n    /* Return Array */\n    toArray() {\n        return this.#stack;\n    }\n}\n
array_stack.ts
/* Stack based on array implementation */\nclass ArrayStack {\n    private stack: number[];\n    constructor() {\n        this.stack = [];\n    }\n\n    /* Get the length of the stack */\n    get size(): number {\n        return this.stack.length;\n    }\n\n    /* Check if the stack is empty */\n    isEmpty(): boolean {\n        return this.stack.length === 0;\n    }\n\n    /* Push */\n    push(num: number): void {\n        this.stack.push(num);\n    }\n\n    /* Pop */\n    pop(): number | undefined {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.stack.pop();\n    }\n\n    /* Return list for printing */\n    top(): number | undefined {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.stack[this.stack.length - 1];\n    }\n\n    /* Return Array */\n    toArray() {\n        return this.stack;\n    }\n}\n
array_stack.dart
/* Stack based on array implementation */\nclass ArrayStack {\n  late List<int> _stack;\n  ArrayStack() {\n    _stack = [];\n  }\n\n  /* Get the length of the stack */\n  int size() {\n    return _stack.length;\n  }\n\n  /* Check if the stack is empty */\n  bool isEmpty() {\n    return _stack.isEmpty;\n  }\n\n  /* Push */\n  void push(int _num) {\n    _stack.add(_num);\n  }\n\n  /* Pop */\n  int pop() {\n    if (isEmpty()) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stack.removeLast();\n  }\n\n  /* Return list for printing */\n  int peek() {\n    if (isEmpty()) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stack.last;\n  }\n\n  /* Convert stack to Array and return */\n  List<int> toArray() => _stack;\n}\n
array_stack.rs
/* Stack based on array implementation */\nstruct ArrayStack<T> {\n    stack: Vec<T>,\n}\n\nimpl<T> ArrayStack<T> {\n    /* Access top of the stack element */\n    fn new() -> ArrayStack<T> {\n        ArrayStack::<T> {\n            stack: Vec::<T>::new(),\n        }\n    }\n\n    /* Get the length of the stack */\n    fn size(&self) -> usize {\n        self.stack.len()\n    }\n\n    /* Check if the stack is empty */\n    fn is_empty(&self) -> bool {\n        self.size() == 0\n    }\n\n    /* Push */\n    fn push(&mut self, num: T) {\n        self.stack.push(num);\n    }\n\n    /* Pop */\n    fn pop(&mut self) -> Option<T> {\n        self.stack.pop()\n    }\n\n    /* Return list for printing */\n    fn peek(&self) -> Option<&T> {\n        if self.is_empty() {\n            panic!(\"Stack is empty\")\n        };\n        self.stack.last()\n    }\n\n    /* Return &Vec */\n    fn to_array(&self) -> &Vec<T> {\n        &self.stack\n    }\n}\n
array_stack.c
/* Stack based on array implementation */\ntypedef struct {\n    int *data;\n    int size;\n} ArrayStack;\n\n/* Constructor */\nArrayStack *newArrayStack() {\n    ArrayStack *stack = malloc(sizeof(ArrayStack));\n    // Initialize with large capacity to avoid expansion\n    stack->data = malloc(sizeof(int) * MAX_SIZE);\n    stack->size = 0;\n    return stack;\n}\n\n/* Destructor */\nvoid delArrayStack(ArrayStack *stack) {\n    free(stack->data);\n    free(stack);\n}\n\n/* Get the length of the stack */\nint size(ArrayStack *stack) {\n    return stack->size;\n}\n\n/* Check if the stack is empty */\nbool isEmpty(ArrayStack *stack) {\n    return stack->size == 0;\n}\n\n/* Push */\nvoid push(ArrayStack *stack, int num) {\n    if (stack->size == MAX_SIZE) {\n        printf(\"Stack is full\\n\");\n        return;\n    }\n    stack->data[stack->size] = num;\n    stack->size++;\n}\n\n/* Return list for printing */\nint peek(ArrayStack *stack) {\n    if (stack->size == 0) {\n        printf(\"Stack is empty\\n\");\n        return INT_MAX;\n    }\n    return stack->data[stack->size - 1];\n}\n\n/* Pop */\nint pop(ArrayStack *stack) {\n    int val = peek(stack);\n    stack->size--;\n    return val;\n}\n
array_stack.kt
/* Stack based on array implementation */\nclass ArrayStack {\n    // Initialize list (dynamic array)\n    private val stack = mutableListOf<Int>()\n\n    /* Get the length of the stack */\n    fun size(): Int {\n        return stack.size\n    }\n\n    /* Check if the stack is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Push */\n    fun push(num: Int) {\n        stack.add(num)\n    }\n\n    /* Pop */\n    fun pop(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stack.removeAt(size() - 1)\n    }\n\n    /* Return list for printing */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stack[size() - 1]\n    }\n\n    /* Convert List to Array and return */\n    fun toArray(): Array<Any> {\n        return stack.toTypedArray()\n    }\n}\n
array_stack.rb
### Stack based on array ###\nclass ArrayStack\n  ### Constructor ###\n  def initialize\n    @stack = []\n  end\n\n  ### Get stack length ###\n  def size\n    @stack.length\n  end\n\n  ### Check if stack is empty ###\n  def is_empty?\n    @stack.empty?\n  end\n\n  ### Push ###\n  def push(item)\n    @stack << item\n  end\n\n  ### Pop ###\n  def pop\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @stack.pop\n  end\n\n  ### Access top element ###\n  def peek\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @stack.last\n  end\n\n  ### Return list for printing ###\n  def to_array\n    @stack\n  end\nend\n
","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#513-comparison-of-the-two-implementations","level":2,"title":"5.1.3   Comparison of the Two Implementations","text":"

Supported Operations

Both implementations support all operations defined by the stack. The array implementation additionally supports random access, but this goes beyond the stack definition and is generally not used.

Time Efficiency

In the array-based implementation, both push and pop operations occur in pre-allocated contiguous memory, which has good cache locality and is therefore more efficient. However, if pushing exceeds the array capacity, it triggers an expansion mechanism, causing the time complexity of that particular push operation to become \\(O(n)\\).

In the linked list-based implementation, list expansion is very flexible, and there is no issue of reduced efficiency due to array expansion. However, the push operation requires initializing a node object and modifying pointers, so it is relatively less efficient. Nevertheless, if the pushed elements are already node objects, the initialization step can be omitted, thereby improving efficiency.

In summary, when the elements pushed and popped are basic data types such as int or double, we can draw the following conclusions:

  • The array-based stack implementation has reduced efficiency when expansion is triggered, but since expansion is an infrequent operation, the average efficiency is higher.
  • The linked list-based stack implementation can provide more stable efficiency performance.

Space Efficiency

When initializing a list, the system allocates an \"initial capacity\" that may exceed the actual need. Additionally, the expansion mechanism typically expands at a specific ratio (e.g., 2x), and the capacity after expansion may also exceed actual needs. Therefore, the array-based stack implementation may cause some space wastage.

However, since linked list nodes need to store additional pointers, the space occupied by linked list nodes is relatively large.

In summary, we cannot simply determine which implementation is more memory-efficient and need to analyze the specific situation.

","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#514-typical-applications-of-stack","level":2,"title":"5.1.4   Typical Applications of Stack","text":"
  • Back and forward in browsers, undo and redo in software. Every time we open a new webpage, the browser pushes the previous page onto the stack, allowing us to return to the previous page via the back operation. The back operation is essentially performing a pop. To support both back and forward, two stacks are needed to work together.
  • Program memory management. Each time a function is called, the system adds a stack frame to the top of the stack to record the function's context information. During recursion, the downward recursive phase continuously performs push operations, while the upward backtracking phase continuously performs pop operations.
","path":["Chapter 5. Stack and Queue","5.1   Stack"],"tags":[]},{"location":"chapter_stack_and_queue/summary/","level":1,"title":"5.4   Summary","text":"","path":["Chapter 5. Stack and Queue","5.4   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • A stack is a data structure that follows the LIFO principle and can be implemented using arrays or linked lists.
  • In terms of time efficiency, the array implementation of a stack has higher average efficiency, but during expansion, the time complexity of a single push operation degrades to \\(O(n)\\). In contrast, the linked list implementation of a stack provides more stable efficiency performance.
  • In terms of space efficiency, the array implementation of a stack may lead to some degree of space wastage. However, it should be noted that the memory space occupied by linked list nodes is larger than that of array elements.
  • A queue is a data structure that follows the FIFO principle and can also be implemented using arrays or linked lists. The conclusions regarding time efficiency and space efficiency comparisons for queues are similar to those for stacks mentioned above.
  • A deque is a queue with greater flexibility that allows adding and removing elements at both ends.
","path":["Chapter 5. Stack and Queue","5.4   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: Is the browser's forward and backward functionality implemented with a doubly linked list?

The forward and backward functionality of a browser is essentially a manifestation of a \"stack.\" When a user visits a new page, that page is added to the top of the stack; when the user clicks the back button, that page is popped from the top of the stack. Using a deque can conveniently implement some additional operations, as mentioned in the \"Deque\" section.

Q: After popping from the stack, do we need to free the memory of the popped node?

If the popped node will still be needed later, then memory does not need to be freed. If it won't be used afterward, languages like Java and Python have automatic garbage collection, so manual memory deallocation is not required; in C and C++, manual memory deallocation is necessary.

Q: A deque seems like two stacks joined together. What is its purpose?

A deque is like a combination of a stack and a queue, or two stacks joined together. It exhibits the logic of both stack and queue, so it can implement all applications of stacks and queues, and is more flexible.

Q: How are undo and redo specifically implemented?

Use two stacks: stack A for undo and stack B for redo.

  1. Whenever the user performs an operation, push this operation onto stack A and clear stack B.
  2. When the user performs \"undo,\" pop the most recent operation from stack A and push it onto stack B.
  3. When the user performs \"redo,\" pop the most recent operation from stack B and push it onto stack A.
","path":["Chapter 5. Stack and Queue","5.4   Summary"],"tags":[]},{"location":"chapter_tree/","level":1,"title":"Chapter 7.   Tree","text":"

Abstract

Towering trees are full of vitality, with deep roots and lush leaves, spreading branches and flourishing.

They show us the vivid form of divide and conquer in data.

","path":["Chapter 7. Tree","Chapter 7.   Tree"],"tags":[]},{"location":"chapter_tree/#chapter-contents","level":2,"title":"Chapter contents","text":"
  • 7.1   Binary Tree
  • 7.2   Binary Tree Traversal
  • 7.3   Array Representation of Tree
  • 7.4   Binary Search Tree
  • 7.5   AVL Tree *
  • 7.6   Summary
","path":["Chapter 7. Tree","Chapter 7.   Tree"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/","level":1,"title":"7.3   Array Representation of Binary Trees","text":"

Under the linked list representation, the storage unit of a binary tree is a node TreeNode, and nodes are connected by pointers. The previous section introduced the basic operations of binary trees under the linked list representation.

So, can we use an array to represent a binary tree? The answer is yes.

","path":["Chapter 7. Tree","7.3   Array Representation of Binary Trees"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#731-representing-perfect-binary-trees","level":2,"title":"7.3.1   Representing Perfect Binary Trees","text":"

Let's analyze a simple case first. Given a perfect binary tree, we store all nodes in an array according to the order of level-order traversal, where each node corresponds to a unique array index.

Based on the characteristics of level-order traversal, we can derive a \"mapping formula\" between parent node index and child node indices: If a node's index is \\(i\\), then its left child index is \\(2i + 1\\) and its right child index is \\(2i + 2\\). Figure 7-12 shows the mapping relationships between various node indices.

Figure 7-12   Array representation of a perfect binary tree

The mapping formula plays a role similar to the node references (pointers) in linked lists. Given any node in the array, we can access its left (right) child node using the mapping formula.

","path":["Chapter 7. Tree","7.3   Array Representation of Binary Trees"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#732-representing-any-binary-tree","level":2,"title":"7.3.2   Representing Any Binary Tree","text":"

Perfect binary trees are a special case; in the middle levels of a binary tree, there are typically many None values. Since the level-order traversal sequence does not include these None values, we cannot infer the number and distribution of None values based on this sequence alone. This means multiple binary tree structures can correspond to the same level-order traversal sequence.

As shown in Figure 7-13, given a non-perfect binary tree, the above method of array representation fails.

Figure 7-13   Level-order traversal sequence corresponds to multiple binary tree possibilities

To solve this problem, we can consider explicitly writing out all None values in the level-order traversal sequence. As shown in Figure 7-14, after this treatment, the level-order traversal sequence can uniquely represent a binary tree. Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
# Array representation of a binary tree\n# Using None to represent empty slots\ntree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]\n
/* Array representation of a binary tree */\n// Using the maximum integer value INT_MAX to mark empty slots\nvector<int> tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};\n
/* Array representation of a binary tree */\n// Using the Integer wrapper class allows for using null to mark empty slots\nInteger[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };\n
/* Array representation of a binary tree */\n// Using nullable int (int?) allows for using null to mark empty slots\nint?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
/* Array representation of a binary tree */\n// Using an any type slice, allowing for nil to mark empty slots\ntree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15}\n
/* Array representation of a binary tree */\n// Using optional Int (Int?) allows for using nil to mark empty slots\nlet tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]\n
/* Array representation of a binary tree */\n// Using null to represent empty slots\nlet tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
/* Array representation of a binary tree */\n// Using null to represent empty slots\nlet tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
/* Array representation of a binary tree */\n// Using nullable int (int?) allows for using null to mark empty slots\nList<int?> tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
/* Array representation of a binary tree */\n// Using None to mark empty slots\nlet tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)];\n
/* Array representation of a binary tree */\n// Using the maximum int value to mark empty slots, therefore, node values must not be INT_MAX\nint tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};\n
/* Array representation of a binary tree */\n// Using null to represent empty slots\nval tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 )\n
### Array representation of a binary tree ###\n# Using nil to represent empty slots\ntree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]\n

Figure 7-14   Array representation of any type of binary tree

It's worth noting that complete binary trees are very well-suited for array representation. Recalling the definition of a complete binary tree, None only appears at the bottom level and towards the right, meaning all None values must appear at the end of the level-order traversal sequence.

This means that when using an array to represent a complete binary tree, it's possible to omit storing all None values, which is very convenient. Figure 7-15 gives an example.

Figure 7-15   Array representation of a complete binary tree

The following code implements a binary tree based on array representation, including the following operations:

  • Given a certain node, obtain its value, left (right) child node, and parent node.
  • Obtain the preorder, inorder, postorder, and level-order traversal sequences.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_binary_tree.py
class ArrayBinaryTree:\n    \"\"\"Binary tree class represented by array\"\"\"\n\n    def __init__(self, arr: list[int | None]):\n        \"\"\"Constructor\"\"\"\n        self._tree = list(arr)\n\n    def size(self):\n        \"\"\"List capacity\"\"\"\n        return len(self._tree)\n\n    def val(self, i: int) -> int | None:\n        \"\"\"Get value of node at index i\"\"\"\n        # If index is out of bounds, return None, representing empty position\n        if i < 0 or i >= self.size():\n            return None\n        return self._tree[i]\n\n    def left(self, i: int) -> int | None:\n        \"\"\"Get index of left child node of node at index i\"\"\"\n        return 2 * i + 1\n\n    def right(self, i: int) -> int | None:\n        \"\"\"Get index of right child node of node at index i\"\"\"\n        return 2 * i + 2\n\n    def parent(self, i: int) -> int | None:\n        \"\"\"Get index of parent node of node at index i\"\"\"\n        return (i - 1) // 2\n\n    def level_order(self) -> list[int]:\n        \"\"\"Level-order traversal\"\"\"\n        self.res = []\n        # Traverse array directly\n        for i in range(self.size()):\n            if self.val(i) is not None:\n                self.res.append(self.val(i))\n        return self.res\n\n    def dfs(self, i: int, order: str):\n        \"\"\"Depth-first traversal\"\"\"\n        if self.val(i) is None:\n            return\n        # Preorder traversal\n        if order == \"pre\":\n            self.res.append(self.val(i))\n        self.dfs(self.left(i), order)\n        # Inorder traversal\n        if order == \"in\":\n            self.res.append(self.val(i))\n        self.dfs(self.right(i), order)\n        # Postorder traversal\n        if order == \"post\":\n            self.res.append(self.val(i))\n\n    def pre_order(self) -> list[int]:\n        \"\"\"Preorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"pre\")\n        return self.res\n\n    def in_order(self) -> list[int]:\n        \"\"\"Inorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"in\")\n        return self.res\n\n    def post_order(self) -> list[int]:\n        \"\"\"Postorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"post\")\n        return self.res\n
array_binary_tree.cpp
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n  public:\n    /* Constructor */\n    ArrayBinaryTree(vector<int> arr) {\n        tree = arr;\n    }\n\n    /* List capacity */\n    int size() {\n        return tree.size();\n    }\n\n    /* Get value of node at index i */\n    int val(int i) {\n        // Return INT_MAX if index out of bounds, representing empty position\n        if (i < 0 || i >= size())\n            return INT_MAX;\n        return tree[i];\n    }\n\n    /* Get index of left child node of node at index i */\n    int left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get index of right child node of node at index i */\n    int right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get index of parent node of node at index i */\n    int parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    vector<int> levelOrder() {\n        vector<int> res;\n        // Traverse array directly\n        for (int i = 0; i < size(); i++) {\n            if (val(i) != INT_MAX)\n                res.push_back(val(i));\n        }\n        return res;\n    }\n\n    /* Preorder traversal */\n    vector<int> preOrder() {\n        vector<int> res;\n        dfs(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    vector<int> inOrder() {\n        vector<int> res;\n        dfs(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    vector<int> postOrder() {\n        vector<int> res;\n        dfs(0, \"post\", res);\n        return res;\n    }\n\n  private:\n    vector<int> tree;\n\n    /* Depth-first traversal */\n    void dfs(int i, string order, vector<int> &res) {\n        // If empty position, return\n        if (val(i) == INT_MAX)\n            return;\n        // Preorder traversal\n        if (order == \"pre\")\n            res.push_back(val(i));\n        dfs(left(i), order, res);\n        // Inorder traversal\n        if (order == \"in\")\n            res.push_back(val(i));\n        dfs(right(i), order, res);\n        // Postorder traversal\n        if (order == \"post\")\n            res.push_back(val(i));\n    }\n};\n
array_binary_tree.java
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n    private List<Integer> tree;\n\n    /* Constructor */\n    public ArrayBinaryTree(List<Integer> arr) {\n        tree = new ArrayList<>(arr);\n    }\n\n    /* List capacity */\n    public int size() {\n        return tree.size();\n    }\n\n    /* Get value of node at index i */\n    public Integer val(int i) {\n        // If index out of bounds, return null to represent empty position\n        if (i < 0 || i >= size())\n            return null;\n        return tree.get(i);\n    }\n\n    /* Get index of left child node of node at index i */\n    public Integer left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get index of right child node of node at index i */\n    public Integer right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get index of parent node of node at index i */\n    public Integer parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    public List<Integer> levelOrder() {\n        List<Integer> res = new ArrayList<>();\n        // Traverse array directly\n        for (int i = 0; i < size(); i++) {\n            if (val(i) != null)\n                res.add(val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    private void dfs(Integer i, String order, List<Integer> res) {\n        // If empty position, return\n        if (val(i) == null)\n            return;\n        // Preorder traversal\n        if (\"pre\".equals(order))\n            res.add(val(i));\n        dfs(left(i), order, res);\n        // Inorder traversal\n        if (\"in\".equals(order))\n            res.add(val(i));\n        dfs(right(i), order, res);\n        // Postorder traversal\n        if (\"post\".equals(order))\n            res.add(val(i));\n    }\n\n    /* Preorder traversal */\n    public List<Integer> preOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    public List<Integer> inOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    public List<Integer> postOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"post\", res);\n        return res;\n    }\n}\n
array_binary_tree.cs
/* Binary tree class represented by array */\nclass ArrayBinaryTree(List<int?> arr) {\n    List<int?> tree = new(arr);\n\n    /* List capacity */\n    public int Size() {\n        return tree.Count;\n    }\n\n    /* Get value of node at index i */\n    public int? Val(int i) {\n        // If index out of bounds, return null to represent empty position\n        if (i < 0 || i >= Size())\n            return null;\n        return tree[i];\n    }\n\n    /* Get index of left child node of node at index i */\n    public int Left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get index of right child node of node at index i */\n    public int Right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get index of parent node of node at index i */\n    public int Parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    public List<int> LevelOrder() {\n        List<int> res = [];\n        // Traverse array directly\n        for (int i = 0; i < Size(); i++) {\n            if (Val(i).HasValue)\n                res.Add(Val(i)!.Value);\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    void DFS(int i, string order, List<int> res) {\n        // If empty position, return\n        if (!Val(i).HasValue)\n            return;\n        // Preorder traversal\n        if (order == \"pre\")\n            res.Add(Val(i)!.Value);\n        DFS(Left(i), order, res);\n        // Inorder traversal\n        if (order == \"in\")\n            res.Add(Val(i)!.Value);\n        DFS(Right(i), order, res);\n        // Postorder traversal\n        if (order == \"post\")\n            res.Add(Val(i)!.Value);\n    }\n\n    /* Preorder traversal */\n    public List<int> PreOrder() {\n        List<int> res = [];\n        DFS(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    public List<int> InOrder() {\n        List<int> res = [];\n        DFS(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    public List<int> PostOrder() {\n        List<int> res = [];\n        DFS(0, \"post\", res);\n        return res;\n    }\n}\n
array_binary_tree.go
/* Binary tree class represented by array */\ntype arrayBinaryTree struct {\n    tree []any\n}\n\n/* Constructor */\nfunc newArrayBinaryTree(arr []any) *arrayBinaryTree {\n    return &arrayBinaryTree{\n        tree: arr,\n    }\n}\n\n/* List capacity */\nfunc (abt *arrayBinaryTree) size() int {\n    return len(abt.tree)\n}\n\n/* Get value of node at index i */\nfunc (abt *arrayBinaryTree) val(i int) any {\n    // If index out of bounds, return null to represent empty position\n    if i < 0 || i >= abt.size() {\n        return nil\n    }\n    return abt.tree[i]\n}\n\n/* Get index of left child node of node at index i */\nfunc (abt *arrayBinaryTree) left(i int) int {\n    return 2*i + 1\n}\n\n/* Get index of right child node of node at index i */\nfunc (abt *arrayBinaryTree) right(i int) int {\n    return 2*i + 2\n}\n\n/* Get index of parent node of node at index i */\nfunc (abt *arrayBinaryTree) parent(i int) int {\n    return (i - 1) / 2\n}\n\n/* Level-order traversal */\nfunc (abt *arrayBinaryTree) levelOrder() []any {\n    var res []any\n    // Traverse array directly\n    for i := 0; i < abt.size(); i++ {\n        if abt.val(i) != nil {\n            res = append(res, abt.val(i))\n        }\n    }\n    return res\n}\n\n/* Depth-first traversal */\nfunc (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) {\n    // If empty position, return\n    if abt.val(i) == nil {\n        return\n    }\n    // Preorder traversal\n    if order == \"pre\" {\n        *res = append(*res, abt.val(i))\n    }\n    abt.dfs(abt.left(i), order, res)\n    // Inorder traversal\n    if order == \"in\" {\n        *res = append(*res, abt.val(i))\n    }\n    abt.dfs(abt.right(i), order, res)\n    // Postorder traversal\n    if order == \"post\" {\n        *res = append(*res, abt.val(i))\n    }\n}\n\n/* Preorder traversal */\nfunc (abt *arrayBinaryTree) preOrder() []any {\n    var res []any\n    abt.dfs(0, \"pre\", &res)\n    return res\n}\n\n/* Inorder traversal */\nfunc (abt *arrayBinaryTree) inOrder() []any {\n    var res []any\n    abt.dfs(0, \"in\", &res)\n    return res\n}\n\n/* Postorder traversal */\nfunc (abt *arrayBinaryTree) postOrder() []any {\n    var res []any\n    abt.dfs(0, \"post\", &res)\n    return res\n}\n
array_binary_tree.swift
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n    private var tree: [Int?]\n\n    /* Constructor */\n    init(arr: [Int?]) {\n        tree = arr\n    }\n\n    /* List capacity */\n    func size() -> Int {\n        tree.count\n    }\n\n    /* Get value of node at index i */\n    func val(i: Int) -> Int? {\n        // If index out of bounds, return null to represent empty position\n        if i < 0 || i >= size() {\n            return nil\n        }\n        return tree[i]\n    }\n\n    /* Get index of left child node of node at index i */\n    func left(i: Int) -> Int {\n        2 * i + 1\n    }\n\n    /* Get index of right child node of node at index i */\n    func right(i: Int) -> Int {\n        2 * i + 2\n    }\n\n    /* Get index of parent node of node at index i */\n    func parent(i: Int) -> Int {\n        (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    func levelOrder() -> [Int] {\n        var res: [Int] = []\n        // Traverse array directly\n        for i in 0 ..< size() {\n            if let val = val(i: i) {\n                res.append(val)\n            }\n        }\n        return res\n    }\n\n    /* Depth-first traversal */\n    private func dfs(i: Int, order: String, res: inout [Int]) {\n        // If empty position, return\n        guard let val = val(i: i) else {\n            return\n        }\n        // Preorder traversal\n        if order == \"pre\" {\n            res.append(val)\n        }\n        dfs(i: left(i: i), order: order, res: &res)\n        // Inorder traversal\n        if order == \"in\" {\n            res.append(val)\n        }\n        dfs(i: right(i: i), order: order, res: &res)\n        // Postorder traversal\n        if order == \"post\" {\n            res.append(val)\n        }\n    }\n\n    /* Preorder traversal */\n    func preOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"pre\", res: &res)\n        return res\n    }\n\n    /* Inorder traversal */\n    func inOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"in\", res: &res)\n        return res\n    }\n\n    /* Postorder traversal */\n    func postOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"post\", res: &res)\n        return res\n    }\n}\n
array_binary_tree.js
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n    #tree;\n\n    /* Constructor */\n    constructor(arr) {\n        this.#tree = arr;\n    }\n\n    /* List capacity */\n    size() {\n        return this.#tree.length;\n    }\n\n    /* Get value of node at index i */\n    val(i) {\n        // If index out of bounds, return null to represent empty position\n        if (i < 0 || i >= this.size()) return null;\n        return this.#tree[i];\n    }\n\n    /* Get index of left child node of node at index i */\n    left(i) {\n        return 2 * i + 1;\n    }\n\n    /* Get index of right child node of node at index i */\n    right(i) {\n        return 2 * i + 2;\n    }\n\n    /* Get index of parent node of node at index i */\n    parent(i) {\n        return Math.floor((i - 1) / 2); // Floor division\n    }\n\n    /* Level-order traversal */\n    levelOrder() {\n        let res = [];\n        // Traverse array directly\n        for (let i = 0; i < this.size(); i++) {\n            if (this.val(i) !== null) res.push(this.val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    #dfs(i, order, res) {\n        // If empty position, return\n        if (this.val(i) === null) return;\n        // Preorder traversal\n        if (order === 'pre') res.push(this.val(i));\n        this.#dfs(this.left(i), order, res);\n        // Inorder traversal\n        if (order === 'in') res.push(this.val(i));\n        this.#dfs(this.right(i), order, res);\n        // Postorder traversal\n        if (order === 'post') res.push(this.val(i));\n    }\n\n    /* Preorder traversal */\n    preOrder() {\n        const res = [];\n        this.#dfs(0, 'pre', res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    inOrder() {\n        const res = [];\n        this.#dfs(0, 'in', res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    postOrder() {\n        const res = [];\n        this.#dfs(0, 'post', res);\n        return res;\n    }\n}\n
array_binary_tree.ts
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n    #tree: (number | null)[];\n\n    /* Constructor */\n    constructor(arr: (number | null)[]) {\n        this.#tree = arr;\n    }\n\n    /* List capacity */\n    size(): number {\n        return this.#tree.length;\n    }\n\n    /* Get value of node at index i */\n    val(i: number): number | null {\n        // If index out of bounds, return null to represent empty position\n        if (i < 0 || i >= this.size()) return null;\n        return this.#tree[i];\n    }\n\n    /* Get index of left child node of node at index i */\n    left(i: number): number {\n        return 2 * i + 1;\n    }\n\n    /* Get index of right child node of node at index i */\n    right(i: number): number {\n        return 2 * i + 2;\n    }\n\n    /* Get index of parent node of node at index i */\n    parent(i: number): number {\n        return Math.floor((i - 1) / 2); // Floor division\n    }\n\n    /* Level-order traversal */\n    levelOrder(): number[] {\n        let res = [];\n        // Traverse array directly\n        for (let i = 0; i < this.size(); i++) {\n            if (this.val(i) !== null) res.push(this.val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    #dfs(i: number, order: Order, res: (number | null)[]): void {\n        // If empty position, return\n        if (this.val(i) === null) return;\n        // Preorder traversal\n        if (order === 'pre') res.push(this.val(i));\n        this.#dfs(this.left(i), order, res);\n        // Inorder traversal\n        if (order === 'in') res.push(this.val(i));\n        this.#dfs(this.right(i), order, res);\n        // Postorder traversal\n        if (order === 'post') res.push(this.val(i));\n    }\n\n    /* Preorder traversal */\n    preOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'pre', res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    inOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'in', res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    postOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'post', res);\n        return res;\n    }\n}\n
array_binary_tree.dart
/* Binary tree class represented by array */\nclass ArrayBinaryTree {\n  late List<int?> _tree;\n\n  /* Constructor */\n  ArrayBinaryTree(this._tree);\n\n  /* List capacity */\n  int size() {\n    return _tree.length;\n  }\n\n  /* Get value of node at index i */\n  int? val(int i) {\n    // If index out of bounds, return null to represent empty position\n    if (i < 0 || i >= size()) {\n      return null;\n    }\n    return _tree[i];\n  }\n\n  /* Get index of left child node of node at index i */\n  int? left(int i) {\n    return 2 * i + 1;\n  }\n\n  /* Get index of right child node of node at index i */\n  int? right(int i) {\n    return 2 * i + 2;\n  }\n\n  /* Get index of parent node of node at index i */\n  int? parent(int i) {\n    return (i - 1) ~/ 2;\n  }\n\n  /* Level-order traversal */\n  List<int> levelOrder() {\n    List<int> res = [];\n    for (int i = 0; i < size(); i++) {\n      if (val(i) != null) {\n        res.add(val(i)!);\n      }\n    }\n    return res;\n  }\n\n  /* Depth-first traversal */\n  void dfs(int i, String order, List<int?> res) {\n    // If empty position, return\n    if (val(i) == null) {\n      return;\n    }\n    // Preorder traversal\n    if (order == 'pre') {\n      res.add(val(i));\n    }\n    dfs(left(i)!, order, res);\n    // Inorder traversal\n    if (order == 'in') {\n      res.add(val(i));\n    }\n    dfs(right(i)!, order, res);\n    // Postorder traversal\n    if (order == 'post') {\n      res.add(val(i));\n    }\n  }\n\n  /* Preorder traversal */\n  List<int?> preOrder() {\n    List<int?> res = [];\n    dfs(0, 'pre', res);\n    return res;\n  }\n\n  /* Inorder traversal */\n  List<int?> inOrder() {\n    List<int?> res = [];\n    dfs(0, 'in', res);\n    return res;\n  }\n\n  /* Postorder traversal */\n  List<int?> postOrder() {\n    List<int?> res = [];\n    dfs(0, 'post', res);\n    return res;\n  }\n}\n
array_binary_tree.rs
/* Binary tree class represented by array */\nstruct ArrayBinaryTree {\n    tree: Vec<Option<i32>>,\n}\n\nimpl ArrayBinaryTree {\n    /* Constructor */\n    fn new(arr: Vec<Option<i32>>) -> Self {\n        Self { tree: arr }\n    }\n\n    /* List capacity */\n    fn size(&self) -> i32 {\n        self.tree.len() as i32\n    }\n\n    /* Get value of node at index i */\n    fn val(&self, i: i32) -> Option<i32> {\n        // If index is out of bounds, return None, representing empty position\n        if i < 0 || i >= self.size() {\n            None\n        } else {\n            self.tree[i as usize]\n        }\n    }\n\n    /* Get index of left child node of node at index i */\n    fn left(&self, i: i32) -> i32 {\n        2 * i + 1\n    }\n\n    /* Get index of right child node of node at index i */\n    fn right(&self, i: i32) -> i32 {\n        2 * i + 2\n    }\n\n    /* Get index of parent node of node at index i */\n    fn parent(&self, i: i32) -> i32 {\n        (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    fn level_order(&self) -> Vec<i32> {\n        self.tree.iter().filter_map(|&x| x).collect()\n    }\n\n    /* Depth-first traversal */\n    fn dfs(&self, i: i32, order: &'static str, res: &mut Vec<i32>) {\n        if self.val(i).is_none() {\n            return;\n        }\n        let val = self.val(i).unwrap();\n        // Preorder traversal\n        if order == \"pre\" {\n            res.push(val);\n        }\n        self.dfs(self.left(i), order, res);\n        // Inorder traversal\n        if order == \"in\" {\n            res.push(val);\n        }\n        self.dfs(self.right(i), order, res);\n        // Postorder traversal\n        if order == \"post\" {\n            res.push(val);\n        }\n    }\n\n    /* Preorder traversal */\n    fn pre_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"pre\", &mut res);\n        res\n    }\n\n    /* Inorder traversal */\n    fn in_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"in\", &mut res);\n        res\n    }\n\n    /* Postorder traversal */\n    fn post_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"post\", &mut res);\n        res\n    }\n}\n
array_binary_tree.c
/* Binary tree structure in array representation */\ntypedef struct {\n    int *tree;\n    int size;\n} ArrayBinaryTree;\n\n/* Constructor */\nArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) {\n    ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree));\n    abt->tree = malloc(sizeof(int) * arrSize);\n    memcpy(abt->tree, arr, sizeof(int) * arrSize);\n    abt->size = arrSize;\n    return abt;\n}\n\n/* Destructor */\nvoid delArrayBinaryTree(ArrayBinaryTree *abt) {\n    free(abt->tree);\n    free(abt);\n}\n\n/* List capacity */\nint size(ArrayBinaryTree *abt) {\n    return abt->size;\n}\n\n/* Get value of node at index i */\nint val(ArrayBinaryTree *abt, int i) {\n    // Return INT_MAX if index out of bounds, representing empty position\n    if (i < 0 || i >= size(abt))\n        return INT_MAX;\n    return abt->tree[i];\n}\n\n/* Level-order traversal */\nint *levelOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    // Traverse array directly\n    for (int i = 0; i < size(abt); i++) {\n        if (val(abt, i) != INT_MAX)\n            res[index++] = val(abt, i);\n    }\n    *returnSize = index;\n    return res;\n}\n\n/* Depth-first traversal */\nvoid dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) {\n    // If empty position, return\n    if (val(abt, i) == INT_MAX)\n        return;\n    // Preorder traversal\n    if (strcmp(order, \"pre\") == 0)\n        res[(*index)++] = val(abt, i);\n    dfs(abt, left(i), order, res, index);\n    // Inorder traversal\n    if (strcmp(order, \"in\") == 0)\n        res[(*index)++] = val(abt, i);\n    dfs(abt, right(i), order, res, index);\n    // Postorder traversal\n    if (strcmp(order, \"post\") == 0)\n        res[(*index)++] = val(abt, i);\n}\n\n/* Preorder traversal */\nint *preOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"pre\", res, &index);\n    *returnSize = index;\n    return res;\n}\n\n/* Inorder traversal */\nint *inOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"in\", res, &index);\n    *returnSize = index;\n    return res;\n}\n\n/* Postorder traversal */\nint *postOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"post\", res, &index);\n    *returnSize = index;\n    return res;\n}\n
array_binary_tree.kt
/* Binary tree class represented by array */\nclass ArrayBinaryTree(val tree: MutableList<Int?>) {\n    /* List capacity */\n    fun size(): Int {\n        return tree.size\n    }\n\n    /* Get value of node at index i */\n    fun _val(i: Int): Int? {\n        // If index out of bounds, return null to represent empty position\n        if (i < 0 || i >= size()) return null\n        return tree[i]\n    }\n\n    /* Get index of left child node of node at index i */\n    fun left(i: Int): Int {\n        return 2 * i + 1\n    }\n\n    /* Get index of right child node of node at index i */\n    fun right(i: Int): Int {\n        return 2 * i + 2\n    }\n\n    /* Get index of parent node of node at index i */\n    fun parent(i: Int): Int {\n        return (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    fun levelOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        // Traverse array directly\n        for (i in 0..<size()) {\n            if (_val(i) != null)\n                res.add(_val(i))\n        }\n        return res\n    }\n\n    /* Depth-first traversal */\n    fun dfs(i: Int, order: String, res: MutableList<Int?>) {\n        // If empty position, return\n        if (_val(i) == null)\n            return\n        // Preorder traversal\n        if (\"pre\" == order)\n            res.add(_val(i))\n        dfs(left(i), order, res)\n        // Inorder traversal\n        if (\"in\" == order)\n            res.add(_val(i))\n        dfs(right(i), order, res)\n        // Postorder traversal\n        if (\"post\" == order)\n            res.add(_val(i))\n    }\n\n    /* Preorder traversal */\n    fun preOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"pre\", res)\n        return res\n    }\n\n    /* Inorder traversal */\n    fun inOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"in\", res)\n        return res\n    }\n\n    /* Postorder traversal */\n    fun postOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"post\", res)\n        return res\n    }\n}\n
array_binary_tree.rb
### Array representation of binary tree class ###\nclass ArrayBinaryTree\n  ### Constructor ###\n  def initialize(arr)\n    @tree = arr.to_a\n  end\n\n  ### List capacity ###\n  def size\n    @tree.length\n  end\n\n  ### Get value of node at index i ###\n  def val(i)\n    # Return nil if index out of bounds, representing empty position\n    return if i < 0 || i >= size\n\n    @tree[i]\n  end\n\n  ### Get left child index of node at index i ###\n  def left(i)\n    2 * i + 1\n  end\n\n  ### Get right child index of node at index i ###\n  def right(i)\n    2 * i + 2\n  end\n\n  ### Get parent node index of node at index i ###\n  def parent(i)\n    (i - 1) / 2\n  end\n\n  ### Level-order traversal ###\n  def level_order\n    @res = []\n\n    # Traverse array directly\n    for i in 0...size\n      @res << val(i) unless val(i).nil?\n    end\n\n    @res\n  end\n\n  ### Depth-first traversal ###\n  def dfs(i, order)\n    return if val(i).nil?\n    # Preorder traversal\n    @res << val(i) if order == :pre\n    dfs(left(i), order)\n    # Inorder traversal\n    @res << val(i) if order == :in\n    dfs(right(i), order)\n    # Postorder traversal\n    @res << val(i) if order == :post\n  end\n\n  ### Pre-order traversal ###\n  def pre_order\n    @res = []\n    dfs(0, :pre)\n    @res\n  end\n\n  ### In-order traversal ###\n  def in_order\n    @res = []\n    dfs(0, :in)\n    @res\n  end\n\n  ### Post-order traversal ###\n  def post_order\n    @res = []\n    dfs(0, :post)\n    @res\n  end\nend\n
","path":["Chapter 7. Tree","7.3   Array Representation of Binary Trees"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#733-advantages-and-limitations","level":2,"title":"7.3.3   Advantages and Limitations","text":"

The array representation of binary trees has the following advantages:

  • Arrays are stored in contiguous memory space, which is cache-friendly, allowing faster access and traversal.
  • It does not require storing pointers, which saves space.
  • It allows random access to nodes.

However, the array representation also has some limitations:

  • Array storage requires contiguous memory space, so it is not suitable for storing trees with a large amount of data.
  • Adding or removing nodes requires array insertion and deletion operations, which have lower efficiency.
  • When there are many None values in the binary tree, the proportion of node data contained in the array is low, leading to lower space utilization.
","path":["Chapter 7. Tree","7.3   Array Representation of Binary Trees"],"tags":[]},{"location":"chapter_tree/avl_tree/","level":1,"title":"7.5   Avl Tree *","text":"

In the \"Binary Search Tree\" section, we mentioned that after multiple insertion and removal operations, a binary search tree may degenerate into a linked list. In this case, the time complexity of all operations degrades from \\(O(\\log n)\\) to \\(O(n)\\).

As shown in Figure 7-24, after two node removal operations, this binary search tree will degrade into a linked list.

Figure 7-24   Degradation of an AVL tree after removing nodes

For example, in the perfect binary tree shown in Figure 7-25, after inserting two nodes, the tree will lean heavily to the left, and the time complexity of search operations will also degrade.

Figure 7-25   Degradation of an AVL tree after inserting nodes

In 1962, G. M. Adelson-Velsky and E. M. Landis proposed the AVL tree in their paper \"An algorithm for the organization of information\". The paper described in detail a series of operations ensuring that after continuously adding and removing nodes, the AVL tree does not degenerate, thus keeping the time complexity of various operations at the \\(O(\\log n)\\) level. In other words, in scenarios requiring frequent insertions, deletions, searches, and modifications, the AVL tree can always maintain efficient data operation performance, making it very valuable in applications.

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#751-common-terminology-in-avl-trees","level":2,"title":"7.5.1   Common Terminology in Avl Trees","text":"

An AVL tree is both a binary search tree and a balanced binary tree, simultaneously satisfying all the properties of these two types of binary trees, hence it is a balanced binary search tree.

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-node-height","level":3,"title":"1.   Node Height","text":"

Since the operations related to AVL trees require obtaining node heights, we need to add a height variable to the node class:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
class TreeNode:\n    \"\"\"AVL tree node\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                 # Node value\n        self.height: int = 0                # Node height\n        self.left: TreeNode | None = None   # Left child reference\n        self.right: TreeNode | None = None  # Right child reference\n
/* AVL tree node */\nstruct TreeNode {\n    int val{};          // Node value\n    int height = 0;     // Node height\n    TreeNode *left{};   // Left child\n    TreeNode *right{};  // Right child\n    TreeNode() = default;\n    explicit TreeNode(int x) : val(x){}\n};\n
/* AVL tree node */\nclass TreeNode {\n    public int val;        // Node value\n    public int height;     // Node height\n    public TreeNode left;  // Left child\n    public TreeNode right; // Right child\n    public TreeNode(int x) { val = x; }\n}\n
/* AVL tree node */\nclass TreeNode(int? x) {\n    public int? val = x;    // Node value\n    public int height;      // Node height\n    public TreeNode? left;  // Left child reference\n    public TreeNode? right; // Right child reference\n}\n
/* AVL tree node */\ntype TreeNode struct {\n    Val    int       // Node value\n    Height int       // Node height\n    Left   *TreeNode // Left child reference\n    Right  *TreeNode // Right child reference\n}\n
/* AVL tree node */\nclass TreeNode {\n    var val: Int // Node value\n    var height: Int // Node height\n    var left: TreeNode? // Left child\n    var right: TreeNode? // Right child\n\n    init(x: Int) {\n        val = x\n        height = 0\n    }\n}\n
/* AVL tree node */\nclass TreeNode {\n    val; // Node value\n    height; // Node height\n    left; // Left child pointer\n    right; // Right child pointer\n    constructor(val, left, right, height) {\n        this.val = val === undefined ? 0 : val;\n        this.height = height === undefined ? 0 : height;\n        this.left = left === undefined ? null : left;\n        this.right = right === undefined ? null : right;\n    }\n}\n
/* AVL tree node */\nclass TreeNode {\n    val: number;            // Node value\n    height: number;         // Node height\n    left: TreeNode | null;  // Left child pointer\n    right: TreeNode | null; // Right child pointer\n    constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) {\n        this.val = val === undefined ? 0 : val;\n        this.height = height === undefined ? 0 : height; \n        this.left = left === undefined ? null : left; \n        this.right = right === undefined ? null : right; \n    }\n}\n
/* AVL tree node */\nclass TreeNode {\n  int val;         // Node value\n  int height;      // Node height\n  TreeNode? left;  // Left child\n  TreeNode? right; // Right child\n  TreeNode(this.val, [this.height = 0, this.left, this.right]);\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* AVL tree node */\nstruct TreeNode {\n    val: i32,                               // Node value\n    height: i32,                            // Node height\n    left: Option<Rc<RefCell<TreeNode>>>,    // Left child\n    right: Option<Rc<RefCell<TreeNode>>>,   // Right child\n}\n\nimpl TreeNode {\n    /* Constructor */\n    fn new(val: i32) -> Rc<RefCell<Self>> {\n        Rc::new(RefCell::new(Self {\n            val,\n            height: 0,\n            left: None,\n            right: None\n        }))\n    }\n}\n
/* AVL tree node */\ntypedef struct TreeNode {\n    int val;\n    int height;\n    struct TreeNode *left;\n    struct TreeNode *right;\n} TreeNode;\n\n/* Constructor */\nTreeNode *newTreeNode(int val) {\n    TreeNode *node;\n\n    node = (TreeNode *)malloc(sizeof(TreeNode));\n    node->val = val;\n    node->height = 0;\n    node->left = NULL;\n    node->right = NULL;\n    return node;\n}\n
/* AVL tree node */\nclass TreeNode(val _val: Int) {  // Node value\n    val height: Int = 0          // Node height\n    val left: TreeNode? = null   // Left child\n    val right: TreeNode? = null  // Right child\n}\n
### AVL tree node class ###\nclass TreeNode\n  attr_accessor :val    # Node value\n  attr_accessor :height # Node height\n  attr_accessor :left   # Left child reference\n  attr_accessor :right  # Right child reference\n\n  def initialize(val)\n    @val = val\n    @height = 0\n  end\nend\n

The \"node height\" refers to the distance from that node to its farthest leaf node, i.e., the number of \"edges\" passed. It is important to note that the height of a leaf node is \\(0\\), and the height of a null node is \\(-1\\). We will create two utility functions for getting and updating the height of a node:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def height(self, node: TreeNode | None) -> int:\n    \"\"\"Get node height\"\"\"\n    # Empty node height is -1, leaf node height is 0\n    if node is not None:\n        return node.height\n    return -1\n\ndef update_height(self, node: TreeNode | None):\n    \"\"\"Update node height\"\"\"\n    # Node height equals the height of the tallest subtree + 1\n    node.height = max([self.height(node.left), self.height(node.right)]) + 1\n
avl_tree.cpp
/* Get node height */\nint height(TreeNode *node) {\n    // Empty node height is -1, leaf node height is 0\n    return node == nullptr ? -1 : node->height;\n}\n\n/* Update node height */\nvoid updateHeight(TreeNode *node) {\n    // Node height equals the height of the tallest subtree + 1\n    node->height = max(height(node->left), height(node->right)) + 1;\n}\n
avl_tree.java
/* Get node height */\nint height(TreeNode node) {\n    // Empty node height is -1, leaf node height is 0\n    return node == null ? -1 : node.height;\n}\n\n/* Update node height */\nvoid updateHeight(TreeNode node) {\n    // Node height equals the height of the tallest subtree + 1\n    node.height = Math.max(height(node.left), height(node.right)) + 1;\n}\n
avl_tree.cs
/* Get node height */\nint Height(TreeNode? node) {\n    // Empty node height is -1, leaf node height is 0\n    return node == null ? -1 : node.height;\n}\n\n/* Update node height */\nvoid UpdateHeight(TreeNode node) {\n    // Node height equals the height of the tallest subtree + 1\n    node.height = Math.Max(Height(node.left), Height(node.right)) + 1;\n}\n
avl_tree.go
/* Get node height */\nfunc (t *aVLTree) height(node *TreeNode) int {\n    // Empty node height is -1, leaf node height is 0\n    if node != nil {\n        return node.Height\n    }\n    return -1\n}\n\n/* Update node height */\nfunc (t *aVLTree) updateHeight(node *TreeNode) {\n    lh := t.height(node.Left)\n    rh := t.height(node.Right)\n    // Node height equals the height of the tallest subtree + 1\n    if lh > rh {\n        node.Height = lh + 1\n    } else {\n        node.Height = rh + 1\n    }\n}\n
avl_tree.swift
/* Get node height */\nfunc height(node: TreeNode?) -> Int {\n    // Empty node height is -1, leaf node height is 0\n    node?.height ?? -1\n}\n\n/* Update node height */\nfunc updateHeight(node: TreeNode?) {\n    // Node height equals the height of the tallest subtree + 1\n    node?.height = max(height(node: node?.left), height(node: node?.right)) + 1\n}\n
avl_tree.js
/* Get node height */\nheight(node) {\n    // Empty node height is -1, leaf node height is 0\n    return node === null ? -1 : node.height;\n}\n\n/* Update node height */\n#updateHeight(node) {\n    // Node height equals the height of the tallest subtree + 1\n    node.height =\n        Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n
avl_tree.ts
/* Get node height */\nheight(node: TreeNode): number {\n    // Empty node height is -1, leaf node height is 0\n    return node === null ? -1 : node.height;\n}\n\n/* Update node height */\nupdateHeight(node: TreeNode): void {\n    // Node height equals the height of the tallest subtree + 1\n    node.height =\n        Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n
avl_tree.dart
/* Get node height */\nint height(TreeNode? node) {\n  // Empty node height is -1, leaf node height is 0\n  return node == null ? -1 : node.height;\n}\n\n/* Update node height */\nvoid updateHeight(TreeNode? node) {\n  // Node height equals the height of the tallest subtree + 1\n  node!.height = max(height(node.left), height(node.right)) + 1;\n}\n
avl_tree.rs
/* Get node height */\nfn height(node: OptionTreeNodeRc) -> i32 {\n    // Empty node height is -1, leaf node height is 0\n    match node {\n        Some(node) => node.borrow().height,\n        None => -1,\n    }\n}\n\n/* Update node height */\nfn update_height(node: OptionTreeNodeRc) {\n    if let Some(node) = node {\n        let left = node.borrow().left.clone();\n        let right = node.borrow().right.clone();\n        // Node height equals the height of the tallest subtree + 1\n        node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1;\n    }\n}\n
avl_tree.c
/* Get node height */\nint height(TreeNode *node) {\n    // Empty node height is -1, leaf node height is 0\n    if (node != NULL) {\n        return node->height;\n    }\n    return -1;\n}\n\n/* Update node height */\nvoid updateHeight(TreeNode *node) {\n    int lh = height(node->left);\n    int rh = height(node->right);\n    // Node height equals the height of the tallest subtree + 1\n    if (lh > rh) {\n        node->height = lh + 1;\n    } else {\n        node->height = rh + 1;\n    }\n}\n
avl_tree.kt
/* Get node height */\nfun height(node: TreeNode?): Int {\n    // Empty node height is -1, leaf node height is 0\n    return node?.height ?: -1\n}\n\n/* Update node height */\nfun updateHeight(node: TreeNode?) {\n    // Node height equals the height of the tallest subtree + 1\n    node?.height = max(height(node?.left), height(node?.right)) + 1\n}\n
avl_tree.rb
### Get node height ###\ndef height(node)\n  # Empty node height is -1, leaf node height is 0\n  return node.height unless node.nil?\n\n  -1\nend\n\n### Update node height ###\ndef update_height(node)\n  # Node height equals the height of the tallest subtree + 1\n  node.height = [height(node.left), height(node.right)].max + 1\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-node-balance-factor","level":3,"title":"2.   Node Balance Factor","text":"

The balance factor of a node is defined as the height of the node's left subtree minus the height of its right subtree, and the balance factor of a null node is defined as \\(0\\). We also encapsulate the function to obtain the node's balance factor for convenient subsequent use:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def balance_factor(self, node: TreeNode | None) -> int:\n    \"\"\"Get balance factor\"\"\"\n    # Empty node balance factor is 0\n    if node is None:\n        return 0\n    # Node balance factor = left subtree height - right subtree height\n    return self.height(node.left) - self.height(node.right)\n
avl_tree.cpp
/* Get balance factor */\nint balanceFactor(TreeNode *node) {\n    // Empty node balance factor is 0\n    if (node == nullptr)\n        return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return height(node->left) - height(node->right);\n}\n
avl_tree.java
/* Get balance factor */\nint balanceFactor(TreeNode node) {\n    // Empty node balance factor is 0\n    if (node == null)\n        return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return height(node.left) - height(node.right);\n}\n
avl_tree.cs
/* Get balance factor */\nint BalanceFactor(TreeNode? node) {\n    // Empty node balance factor is 0\n    if (node == null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return Height(node.left) - Height(node.right);\n}\n
avl_tree.go
/* Get balance factor */\nfunc (t *aVLTree) balanceFactor(node *TreeNode) int {\n    // Empty node balance factor is 0\n    if node == nil {\n        return 0\n    }\n    // Node balance factor = left subtree height - right subtree height\n    return t.height(node.Left) - t.height(node.Right)\n}\n
avl_tree.swift
/* Get balance factor */\nfunc balanceFactor(node: TreeNode?) -> Int {\n    // Empty node balance factor is 0\n    guard let node = node else { return 0 }\n    // Node balance factor = left subtree height - right subtree height\n    return height(node: node.left) - height(node: node.right)\n}\n
avl_tree.js
/* Get balance factor */\nbalanceFactor(node) {\n    // Empty node balance factor is 0\n    if (node === null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return this.height(node.left) - this.height(node.right);\n}\n
avl_tree.ts
/* Get balance factor */\nbalanceFactor(node: TreeNode): number {\n    // Empty node balance factor is 0\n    if (node === null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return this.height(node.left) - this.height(node.right);\n}\n
avl_tree.dart
/* Get balance factor */\nint balanceFactor(TreeNode? node) {\n  // Empty node balance factor is 0\n  if (node == null) return 0;\n  // Node balance factor = left subtree height - right subtree height\n  return height(node.left) - height(node.right);\n}\n
avl_tree.rs
/* Get balance factor */\nfn balance_factor(node: OptionTreeNodeRc) -> i32 {\n    match node {\n        // Empty node balance factor is 0\n        None => 0,\n        // Node balance factor = left subtree height - right subtree height\n        Some(node) => {\n            Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone())\n        }\n    }\n}\n
avl_tree.c
/* Get balance factor */\nint balanceFactor(TreeNode *node) {\n    // Empty node balance factor is 0\n    if (node == NULL) {\n        return 0;\n    }\n    // Node balance factor = left subtree height - right subtree height\n    return height(node->left) - height(node->right);\n}\n
avl_tree.kt
/* Get balance factor */\nfun balanceFactor(node: TreeNode?): Int {\n    // Empty node balance factor is 0\n    if (node == null) return 0\n    // Node balance factor = left subtree height - right subtree height\n    return height(node.left) - height(node.right)\n}\n
avl_tree.rb
### Get balance factor ###\ndef balance_factor(node)\n  # Empty node balance factor is 0\n  return 0 if node.nil?\n\n  # Node balance factor = left subtree height - right subtree height\n  height(node.left) - height(node.right)\nend\n

Tip

Let the balance factor be \\(f\\), then the balance factor of any node in an AVL tree satisfies \\(-1 \\le f \\le 1\\).

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#752-rotations-in-avl-trees","level":2,"title":"7.5.2   Rotations in Avl Trees","text":"

The characteristic of AVL trees lies in the \"rotation\" operation, which can restore balance to unbalanced nodes without affecting the inorder traversal sequence of the binary tree. In other words, rotation operations can both maintain the property of a \"binary search tree\" and make the tree return to a \"balanced binary tree\".

We call nodes with a balance factor absolute value \\(> 1\\) \"unbalanced nodes\". Depending on the imbalance situation, rotation operations are divided into four types: right rotation, left rotation, left rotation then right rotation, and right rotation then left rotation. Below we describe these rotation operations in detail.

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-right-rotation","level":3,"title":"1.   Right Rotation","text":"

As shown in Figure 7-26, the value below the node is the balance factor. From bottom to top, the first unbalanced node in the binary tree is \"node 3\". We focus on the subtree with this unbalanced node as the root, denoting the node as node and its left child as child, and perform a \"right rotation\" operation. After the right rotation is completed, the subtree regains balance and still maintains the properties of a binary search tree.

<1><2><3><4>

Figure 7-26   Steps of right rotation

As shown in Figure 7-27, when the child node has a right child (denoted as grand_child), a step needs to be added in the right rotation: set grand_child as the left child of node.

Figure 7-27   Right rotation with grand_child

\"Right rotation\" is a figurative term; in practice, it is achieved by modifying node pointers, as shown in the following code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def right_rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Right rotation operation\"\"\"\n    child = node.left\n    grand_child = child.right\n    # Using child as pivot, rotate node to the right\n    child.right = node\n    node.left = grand_child\n    # Update node height\n    self.update_height(node)\n    self.update_height(child)\n    # Return root node of subtree after rotation\n    return child\n
avl_tree.cpp
/* Right rotation operation */\nTreeNode *rightRotate(TreeNode *node) {\n    TreeNode *child = node->left;\n    TreeNode *grandChild = child->right;\n    // Using child as pivot, rotate node to the right\n    child->right = node;\n    node->left = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.java
/* Right rotation operation */\nTreeNode rightRotate(TreeNode node) {\n    TreeNode child = node.left;\n    TreeNode grandChild = child.right;\n    // Using child as pivot, rotate node to the right\n    child.right = node;\n    node.left = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.cs
/* Right rotation operation */\nTreeNode? RightRotate(TreeNode? node) {\n    TreeNode? child = node?.left;\n    TreeNode? grandChild = child?.right;\n    // Using child as pivot, rotate node to the right\n    child.right = node;\n    node.left = grandChild;\n    // Update node height\n    UpdateHeight(node);\n    UpdateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.go
/* Right rotation operation */\nfunc (t *aVLTree) rightRotate(node *TreeNode) *TreeNode {\n    child := node.Left\n    grandChild := child.Right\n    // Using child as pivot, rotate node to the right\n    child.Right = node\n    node.Left = grandChild\n    // Update node height\n    t.updateHeight(node)\n    t.updateHeight(child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.swift
/* Right rotation operation */\nfunc rightRotate(node: TreeNode?) -> TreeNode? {\n    let child = node?.left\n    let grandChild = child?.right\n    // Using child as pivot, rotate node to the right\n    child?.right = node\n    node?.left = grandChild\n    // Update node height\n    updateHeight(node: node)\n    updateHeight(node: child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.js
/* Right rotation operation */\n#rightRotate(node) {\n    const child = node.left;\n    const grandChild = child.right;\n    // Using child as pivot, rotate node to the right\n    child.right = node;\n    node.left = grandChild;\n    // Update node height\n    this.#updateHeight(node);\n    this.#updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.ts
/* Right rotation operation */\nrightRotate(node: TreeNode): TreeNode {\n    const child = node.left;\n    const grandChild = child.right;\n    // Using child as pivot, rotate node to the right\n    child.right = node;\n    node.left = grandChild;\n    // Update node height\n    this.updateHeight(node);\n    this.updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.dart
/* Right rotation operation */\nTreeNode? rightRotate(TreeNode? node) {\n  TreeNode? child = node!.left;\n  TreeNode? grandChild = child!.right;\n  // Using child as pivot, rotate node to the right\n  child.right = node;\n  node.left = grandChild;\n  // Update node height\n  updateHeight(node);\n  updateHeight(child);\n  // Return root node of subtree after rotation\n  return child;\n}\n
avl_tree.rs
/* Right rotation operation */\nfn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    match node {\n        Some(node) => {\n            let child = node.borrow().left.clone().unwrap();\n            let grand_child = child.borrow().right.clone();\n            // Using child as pivot, rotate node to the right\n            child.borrow_mut().right = Some(node.clone());\n            node.borrow_mut().left = grand_child;\n            // Update node height\n            Self::update_height(Some(node));\n            Self::update_height(Some(child.clone()));\n            // Return root node of subtree after rotation\n            Some(child)\n        }\n        None => None,\n    }\n}\n
avl_tree.c
/* Right rotation operation */\nTreeNode *rightRotate(TreeNode *node) {\n    TreeNode *child, *grandChild;\n    child = node->left;\n    grandChild = child->right;\n    // Using child as pivot, rotate node to the right\n    child->right = node;\n    node->left = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.kt
/* Right rotation operation */\nfun rightRotate(node: TreeNode?): TreeNode {\n    val child = node!!.left\n    val grandChild = child!!.right\n    // Using child as pivot, rotate node to the right\n    child.right = node\n    node.left = grandChild\n    // Update node height\n    updateHeight(node)\n    updateHeight(child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.rb
### Right rotation ###\ndef right_rotate(node)\n  child = node.left\n  grand_child = child.right\n  # Using child as pivot, rotate node to the right\n  child.right = node\n  node.left = grand_child\n  # Update node height\n  update_height(node)\n  update_height(child)\n  # Return root node of subtree after rotation\n  child\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-left-rotation","level":3,"title":"2.   Left Rotation","text":"

Correspondingly, if considering the \"mirror\" of the above unbalanced binary tree, the \"left rotation\" operation shown in Figure 7-28 needs to be performed.

Figure 7-28   Left rotation operation

Similarly, as shown in Figure 7-29, when the child node has a left child (denoted as grand_child), a step needs to be added in the left rotation: set grand_child as the right child of node.

Figure 7-29   Left rotation with grand_child

It can be observed that right rotation and left rotation operations are mirror symmetric in logic, and the two imbalance cases they solve are also symmetric. Based on symmetry, we only need to replace all left in the right rotation implementation code with right, and all right with left, to obtain the left rotation implementation code:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def left_rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Left rotation operation\"\"\"\n    child = node.right\n    grand_child = child.left\n    # Using child as pivot, rotate node to the left\n    child.left = node\n    node.right = grand_child\n    # Update node height\n    self.update_height(node)\n    self.update_height(child)\n    # Return root node of subtree after rotation\n    return child\n
avl_tree.cpp
/* Left rotation operation */\nTreeNode *leftRotate(TreeNode *node) {\n    TreeNode *child = node->right;\n    TreeNode *grandChild = child->left;\n    // Using child as pivot, rotate node to the left\n    child->left = node;\n    node->right = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.java
/* Left rotation operation */\nTreeNode leftRotate(TreeNode node) {\n    TreeNode child = node.right;\n    TreeNode grandChild = child.left;\n    // Using child as pivot, rotate node to the left\n    child.left = node;\n    node.right = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.cs
/* Left rotation operation */\nTreeNode? LeftRotate(TreeNode? node) {\n    TreeNode? child = node?.right;\n    TreeNode? grandChild = child?.left;\n    // Using child as pivot, rotate node to the left\n    child.left = node;\n    node.right = grandChild;\n    // Update node height\n    UpdateHeight(node);\n    UpdateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.go
/* Left rotation operation */\nfunc (t *aVLTree) leftRotate(node *TreeNode) *TreeNode {\n    child := node.Right\n    grandChild := child.Left\n    // Using child as pivot, rotate node to the left\n    child.Left = node\n    node.Right = grandChild\n    // Update node height\n    t.updateHeight(node)\n    t.updateHeight(child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.swift
/* Left rotation operation */\nfunc leftRotate(node: TreeNode?) -> TreeNode? {\n    let child = node?.right\n    let grandChild = child?.left\n    // Using child as pivot, rotate node to the left\n    child?.left = node\n    node?.right = grandChild\n    // Update node height\n    updateHeight(node: node)\n    updateHeight(node: child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.js
/* Left rotation operation */\n#leftRotate(node) {\n    const child = node.right;\n    const grandChild = child.left;\n    // Using child as pivot, rotate node to the left\n    child.left = node;\n    node.right = grandChild;\n    // Update node height\n    this.#updateHeight(node);\n    this.#updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.ts
/* Left rotation operation */\nleftRotate(node: TreeNode): TreeNode {\n    const child = node.right;\n    const grandChild = child.left;\n    // Using child as pivot, rotate node to the left\n    child.left = node;\n    node.right = grandChild;\n    // Update node height\n    this.updateHeight(node);\n    this.updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.dart
/* Left rotation operation */\nTreeNode? leftRotate(TreeNode? node) {\n  TreeNode? child = node!.right;\n  TreeNode? grandChild = child!.left;\n  // Using child as pivot, rotate node to the left\n  child.left = node;\n  node.right = grandChild;\n  // Update node height\n  updateHeight(node);\n  updateHeight(child);\n  // Return root node of subtree after rotation\n  return child;\n}\n
avl_tree.rs
/* Left rotation operation */\nfn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    match node {\n        Some(node) => {\n            let child = node.borrow().right.clone().unwrap();\n            let grand_child = child.borrow().left.clone();\n            // Using child as pivot, rotate node to the left\n            child.borrow_mut().left = Some(node.clone());\n            node.borrow_mut().right = grand_child;\n            // Update node height\n            Self::update_height(Some(node));\n            Self::update_height(Some(child.clone()));\n            // Return root node of subtree after rotation\n            Some(child)\n        }\n        None => None,\n    }\n}\n
avl_tree.c
/* Left rotation operation */\nTreeNode *leftRotate(TreeNode *node) {\n    TreeNode *child, *grandChild;\n    child = node->right;\n    grandChild = child->left;\n    // Using child as pivot, rotate node to the left\n    child->left = node;\n    node->right = grandChild;\n    // Update node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return root node of subtree after rotation\n    return child;\n}\n
avl_tree.kt
/* Left rotation operation */\nfun leftRotate(node: TreeNode?): TreeNode {\n    val child = node!!.right\n    val grandChild = child!!.left\n    // Using child as pivot, rotate node to the left\n    child.left = node\n    node.right = grandChild\n    // Update node height\n    updateHeight(node)\n    updateHeight(child)\n    // Return root node of subtree after rotation\n    return child\n}\n
avl_tree.rb
### Left rotation ###\ndef left_rotate(node)\n  child = node.right\n  grand_child = child.left\n  # Using child as pivot, rotate node to the left\n  child.left = node\n  node.right = grand_child\n  # Update node height\n  update_height(node)\n  update_height(child)\n  # Return root node of subtree after rotation\n  child\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#3-left-rotation-then-right-rotation","level":3,"title":"3.   Left Rotation Then Right Rotation","text":"

For the unbalanced node 3 in Figure 7-30, using either left rotation or right rotation alone cannot restore the subtree to balance. In this case, a \"left rotation\" needs to be performed on child first, followed by a \"right rotation\" on node.

Figure 7-30   Left-right rotation

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#4-right-rotation-then-left-rotation","level":3,"title":"4.   Right Rotation Then Left Rotation","text":"

As shown in Figure 7-31, for the mirror case of the above unbalanced binary tree, a \"right rotation\" needs to be performed on child first, then a \"left rotation\" on node.

Figure 7-31   Right-left rotation

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#5-choice-of-rotation","level":3,"title":"5.   Choice of Rotation","text":"

The four imbalances shown in Figure 7-32 correspond one-to-one with the above cases, requiring right rotation, left rotation then right rotation, right rotation then left rotation, and left rotation operations respectively.

Figure 7-32   The four rotation cases of AVL tree

As shown in Table 7-3, we determine which case the unbalanced node belongs to by judging the signs of the balance factor of the unbalanced node and the balance factor of its taller-side child node.

Table 7-3   Conditions for Choosing Among the Four Rotation Cases

Balance factor of the unbalanced node Balance factor of the child node Rotation method to apply \\(> 1\\) (left-leaning tree) \\(\\geq 0\\) Right rotation \\(> 1\\) (left-leaning tree) \\(<0\\) Left rotation then right rotation \\(< -1\\) (right-leaning tree) \\(\\leq 0\\) Left rotation \\(< -1\\) (right-leaning tree) \\(>0\\) Right rotation then left rotation

For ease of use, we encapsulate the rotation operations into a function. With this function, we can perform rotations for various imbalance situations, restoring balance to unbalanced nodes. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Perform rotation operation to restore balance to this subtree\"\"\"\n    # Get balance factor of node\n    balance_factor = self.balance_factor(node)\n    # Left-leaning tree\n    if balance_factor > 1:\n        if self.balance_factor(node.left) >= 0:\n            # Right rotation\n            return self.right_rotate(node)\n        else:\n            # First left rotation then right rotation\n            node.left = self.left_rotate(node.left)\n            return self.right_rotate(node)\n    # Right-leaning tree\n    elif balance_factor < -1:\n        if self.balance_factor(node.right) <= 0:\n            # Left rotation\n            return self.left_rotate(node)\n        else:\n            # First right rotation then left rotation\n            node.right = self.right_rotate(node.right)\n            return self.left_rotate(node)\n    # Balanced tree, no rotation needed, return directly\n    return node\n
avl_tree.cpp
/* Perform rotation operation to restore balance to this subtree */\nTreeNode *rotate(TreeNode *node) {\n    // Get balance factor of node\n    int _balanceFactor = balanceFactor(node);\n    // Left-leaning tree\n    if (_balanceFactor > 1) {\n        if (balanceFactor(node->left) >= 0) {\n            // Right rotation\n            return rightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node->left = leftRotate(node->left);\n            return rightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (_balanceFactor < -1) {\n        if (balanceFactor(node->right) <= 0) {\n            // Left rotation\n            return leftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node->right = rightRotate(node->right);\n            return leftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.java
/* Perform rotation operation to restore balance to this subtree */\nTreeNode rotate(TreeNode node) {\n    // Get balance factor of node\n    int balanceFactor = balanceFactor(node);\n    // Left-leaning tree\n    if (balanceFactor > 1) {\n        if (balanceFactor(node.left) >= 0) {\n            // Right rotation\n            return rightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node.left = leftRotate(node.left);\n            return rightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (balanceFactor < -1) {\n        if (balanceFactor(node.right) <= 0) {\n            // Left rotation\n            return leftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node.right = rightRotate(node.right);\n            return leftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.cs
/* Perform rotation operation to restore balance to this subtree */\nTreeNode? Rotate(TreeNode? node) {\n    // Get balance factor of node\n    int balanceFactorInt = BalanceFactor(node);\n    // Left-leaning tree\n    if (balanceFactorInt > 1) {\n        if (BalanceFactor(node?.left) >= 0) {\n            // Right rotation\n            return RightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node!.left = LeftRotate(node!.left);\n            return RightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (balanceFactorInt < -1) {\n        if (BalanceFactor(node?.right) <= 0) {\n            // Left rotation\n            return LeftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node!.right = RightRotate(node!.right);\n            return LeftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.go
/* Perform rotation operation to restore balance to this subtree */\nfunc (t *aVLTree) rotate(node *TreeNode) *TreeNode {\n    // Get balance factor of node\n    // Go recommends short variables, here bf refers to t.balanceFactor\n    bf := t.balanceFactor(node)\n    // Left-leaning tree\n    if bf > 1 {\n        if t.balanceFactor(node.Left) >= 0 {\n            // Right rotation\n            return t.rightRotate(node)\n        } else {\n            // First left rotation then right rotation\n            node.Left = t.leftRotate(node.Left)\n            return t.rightRotate(node)\n        }\n    }\n    // Right-leaning tree\n    if bf < -1 {\n        if t.balanceFactor(node.Right) <= 0 {\n            // Left rotation\n            return t.leftRotate(node)\n        } else {\n            // First right rotation then left rotation\n            node.Right = t.rightRotate(node.Right)\n            return t.leftRotate(node)\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node\n}\n
avl_tree.swift
/* Perform rotation operation to restore balance to this subtree */\nfunc rotate(node: TreeNode?) -> TreeNode? {\n    // Get balance factor of node\n    let balanceFactor = balanceFactor(node: node)\n    // Left-leaning tree\n    if balanceFactor > 1 {\n        if self.balanceFactor(node: node?.left) >= 0 {\n            // Right rotation\n            return rightRotate(node: node)\n        } else {\n            // First left rotation then right rotation\n            node?.left = leftRotate(node: node?.left)\n            return rightRotate(node: node)\n        }\n    }\n    // Right-leaning tree\n    if balanceFactor < -1 {\n        if self.balanceFactor(node: node?.right) <= 0 {\n            // Left rotation\n            return leftRotate(node: node)\n        } else {\n            // First right rotation then left rotation\n            node?.right = rightRotate(node: node?.right)\n            return leftRotate(node: node)\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node\n}\n
avl_tree.js
/* Perform rotation operation to restore balance to this subtree */\n#rotate(node) {\n    // Get balance factor of node\n    const balanceFactor = this.balanceFactor(node);\n    // Left-leaning tree\n    if (balanceFactor > 1) {\n        if (this.balanceFactor(node.left) >= 0) {\n            // Right rotation\n            return this.#rightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node.left = this.#leftRotate(node.left);\n            return this.#rightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (balanceFactor < -1) {\n        if (this.balanceFactor(node.right) <= 0) {\n            // Left rotation\n            return this.#leftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node.right = this.#rightRotate(node.right);\n            return this.#leftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.ts
/* Perform rotation operation to restore balance to this subtree */\nrotate(node: TreeNode): TreeNode {\n    // Get balance factor of node\n    const balanceFactor = this.balanceFactor(node);\n    // Left-leaning tree\n    if (balanceFactor > 1) {\n        if (this.balanceFactor(node.left) >= 0) {\n            // Right rotation\n            return this.rightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node.left = this.leftRotate(node.left);\n            return this.rightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (balanceFactor < -1) {\n        if (this.balanceFactor(node.right) <= 0) {\n            // Left rotation\n            return this.leftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node.right = this.rightRotate(node.right);\n            return this.leftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.dart
/* Perform rotation operation to restore balance to this subtree */\nTreeNode? rotate(TreeNode? node) {\n  // Get balance factor of node\n  int factor = balanceFactor(node);\n  // Left-leaning tree\n  if (factor > 1) {\n    if (balanceFactor(node!.left) >= 0) {\n      // Right rotation\n      return rightRotate(node);\n    } else {\n      // First left rotation then right rotation\n      node.left = leftRotate(node.left);\n      return rightRotate(node);\n    }\n  }\n  // Right-leaning tree\n  if (factor < -1) {\n    if (balanceFactor(node!.right) <= 0) {\n      // Left rotation\n      return leftRotate(node);\n    } else {\n      // First right rotation then left rotation\n      node.right = rightRotate(node.right);\n      return leftRotate(node);\n    }\n  }\n  // Balanced tree, no rotation needed, return directly\n  return node;\n}\n
avl_tree.rs
/* Perform rotation operation to restore balance to this subtree */\nfn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    // Get balance factor of node\n    let balance_factor = Self::balance_factor(node.clone());\n    // Left-leaning tree\n    if balance_factor > 1 {\n        let node = node.unwrap();\n        if Self::balance_factor(node.borrow().left.clone()) >= 0 {\n            // Right rotation\n            Self::right_rotate(Some(node))\n        } else {\n            // First left rotation then right rotation\n            let left = node.borrow().left.clone();\n            node.borrow_mut().left = Self::left_rotate(left);\n            Self::right_rotate(Some(node))\n        }\n    }\n    // Right-leaning tree\n    else if balance_factor < -1 {\n        let node = node.unwrap();\n        if Self::balance_factor(node.borrow().right.clone()) <= 0 {\n            // Left rotation\n            Self::left_rotate(Some(node))\n        } else {\n            // First right rotation then left rotation\n            let right = node.borrow().right.clone();\n            node.borrow_mut().right = Self::right_rotate(right);\n            Self::left_rotate(Some(node))\n        }\n    } else {\n        // Balanced tree, no rotation needed, return directly\n        node\n    }\n}\n
avl_tree.c
/* Perform rotation operation to restore balance to this subtree */\nTreeNode *rotate(TreeNode *node) {\n    // Get balance factor of node\n    int bf = balanceFactor(node);\n    // Left-leaning tree\n    if (bf > 1) {\n        if (balanceFactor(node->left) >= 0) {\n            // Right rotation\n            return rightRotate(node);\n        } else {\n            // First left rotation then right rotation\n            node->left = leftRotate(node->left);\n            return rightRotate(node);\n        }\n    }\n    // Right-leaning tree\n    if (bf < -1) {\n        if (balanceFactor(node->right) <= 0) {\n            // Left rotation\n            return leftRotate(node);\n        } else {\n            // First right rotation then left rotation\n            node->right = rightRotate(node->right);\n            return leftRotate(node);\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node;\n}\n
avl_tree.kt
/* Perform rotation operation to restore balance to this subtree */\nfun rotate(node: TreeNode): TreeNode {\n    // Get balance factor of node\n    val balanceFactor = balanceFactor(node)\n    // Left-leaning tree\n    if (balanceFactor > 1) {\n        if (balanceFactor(node.left) >= 0) {\n            // Right rotation\n            return rightRotate(node)\n        } else {\n            // First left rotation then right rotation\n            node.left = leftRotate(node.left)\n            return rightRotate(node)\n        }\n    }\n    // Right-leaning tree\n    if (balanceFactor < -1) {\n        if (balanceFactor(node.right) <= 0) {\n            // Left rotation\n            return leftRotate(node)\n        } else {\n            // First right rotation then left rotation\n            node.right = rightRotate(node.right)\n            return leftRotate(node)\n        }\n    }\n    // Balanced tree, no rotation needed, return directly\n    return node\n}\n
avl_tree.rb
### Perform rotation to rebalance subtree ###\ndef rotate(node)\n  # Get balance factor of node\n  balance_factor = balance_factor(node)\n  # Left-heavy tree\n  if balance_factor > 1\n    if balance_factor(node.left) >= 0\n      # Right rotation\n      return right_rotate(node)\n    else\n      # First left rotation then right rotation\n      node.left = left_rotate(node.left)\n      return right_rotate(node)\n    end\n  # Right-heavy tree\n  elsif balance_factor < -1\n    if balance_factor(node.right) <= 0\n      # Left rotation\n      return left_rotate(node)\n    else\n      # First right rotation then left rotation\n      node.right = right_rotate(node.right)\n      return left_rotate(node)\n    end\n  end\n  # Balanced tree, no rotation needed, return directly\n  node\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#753-common-operations-in-avl-trees","level":2,"title":"7.5.3   Common Operations in Avl Trees","text":"","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-node-insertion","level":3,"title":"1.   Node Insertion","text":"

The node insertion operation in AVL trees is similar in principle to that in binary search trees. The only difference is that after inserting a node in an AVL tree, a series of unbalanced nodes may appear on the path from that node to the root. Therefore, we need to start from this node and perform rotation operations from bottom to top, restoring balance to all unbalanced nodes. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def insert(self, val):\n    \"\"\"Insert node\"\"\"\n    self._root = self.insert_helper(self._root, val)\n\ndef insert_helper(self, node: TreeNode | None, val: int) -> TreeNode:\n    \"\"\"Recursively insert node (helper method)\"\"\"\n    if node is None:\n        return TreeNode(val)\n    # 1. Find insertion position and insert node\n    if val < node.val:\n        node.left = self.insert_helper(node.left, val)\n    elif val > node.val:\n        node.right = self.insert_helper(node.right, val)\n    else:\n        # Duplicate node not inserted, return directly\n        return node\n    # Update node height\n    self.update_height(node)\n    # 2. Perform rotation operation to restore balance to this subtree\n    return self.rotate(node)\n
avl_tree.cpp
/* Insert node */\nvoid insert(int val) {\n    root = insertHelper(root, val);\n}\n\n/* Recursively insert node (helper method) */\nTreeNode *insertHelper(TreeNode *node, int val) {\n    if (node == nullptr)\n        return new TreeNode(val);\n    /* 1. Find insertion position and insert node */\n    if (val < node->val)\n        node->left = insertHelper(node->left, val);\n    else if (val > node->val)\n        node->right = insertHelper(node->right, val);\n    else\n        return node;    // Duplicate node not inserted, return directly\n    updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.java
/* Insert node */\nvoid insert(int val) {\n    root = insertHelper(root, val);\n}\n\n/* Recursively insert node (helper method) */\nTreeNode insertHelper(TreeNode node, int val) {\n    if (node == null)\n        return new TreeNode(val);\n    /* 1. Find insertion position and insert node */\n    if (val < node.val)\n        node.left = insertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = insertHelper(node.right, val);\n    else\n        return node; // Duplicate node not inserted, return directly\n    updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.cs
/* Insert node */\nvoid Insert(int val) {\n    root = InsertHelper(root, val);\n}\n\n/* Recursively insert node (helper method) */\nTreeNode? InsertHelper(TreeNode? node, int val) {\n    if (node == null) return new TreeNode(val);\n    /* 1. Find insertion position and insert node */\n    if (val < node.val)\n        node.left = InsertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = InsertHelper(node.right, val);\n    else\n        return node;     // Duplicate node not inserted, return directly\n    UpdateHeight(node);  // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = Rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.go
/* Insert node */\nfunc (t *aVLTree) insert(val int) {\n    t.root = t.insertHelper(t.root, val)\n}\n\n/* Recursively insert node (helper function) */\nfunc (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode {\n    if node == nil {\n        return NewTreeNode(val)\n    }\n    /* 1. Find insertion position and insert node */\n    if val < node.Val.(int) {\n        node.Left = t.insertHelper(node.Left, val)\n    } else if val > node.Val.(int) {\n        node.Right = t.insertHelper(node.Right, val)\n    } else {\n        // Duplicate node not inserted, return directly\n        return node\n    }\n    // Update node height\n    t.updateHeight(node)\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = t.rotate(node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.swift
/* Insert node */\nfunc insert(val: Int) {\n    root = insertHelper(node: root, val: val)\n}\n\n/* Recursively insert node (helper method) */\nfunc insertHelper(node: TreeNode?, val: Int) -> TreeNode? {\n    var node = node\n    if node == nil {\n        return TreeNode(x: val)\n    }\n    /* 1. Find insertion position and insert node */\n    if val < node!.val {\n        node?.left = insertHelper(node: node?.left, val: val)\n    } else if val > node!.val {\n        node?.right = insertHelper(node: node?.right, val: val)\n    } else {\n        return node // Duplicate node not inserted, return directly\n    }\n    updateHeight(node: node) // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node: node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.js
/* Insert node */\ninsert(val) {\n    this.root = this.#insertHelper(this.root, val);\n}\n\n/* Recursively insert node (helper method) */\n#insertHelper(node, val) {\n    if (node === null) return new TreeNode(val);\n    /* 1. Find insertion position and insert node */\n    if (val < node.val) node.left = this.#insertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = this.#insertHelper(node.right, val);\n    else return node; // Duplicate node not inserted, return directly\n    this.#updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = this.#rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.ts
/* Insert node */\ninsert(val: number): void {\n    this.root = this.insertHelper(this.root, val);\n}\n\n/* Recursively insert node (helper method) */\ninsertHelper(node: TreeNode, val: number): TreeNode {\n    if (node === null) return new TreeNode(val);\n    /* 1. Find insertion position and insert node */\n    if (val < node.val) {\n        node.left = this.insertHelper(node.left, val);\n    } else if (val > node.val) {\n        node.right = this.insertHelper(node.right, val);\n    } else {\n        return node; // Duplicate node not inserted, return directly\n    }\n    this.updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = this.rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.dart
/* Insert node */\nvoid insert(int val) {\n  root = insertHelper(root, val);\n}\n\n/* Recursively insert node (helper method) */\nTreeNode? insertHelper(TreeNode? node, int val) {\n  if (node == null) return TreeNode(val);\n  /* 1. Find insertion position and insert node */\n  if (val < node.val)\n    node.left = insertHelper(node.left, val);\n  else if (val > node.val)\n    node.right = insertHelper(node.right, val);\n  else\n    return node; // Duplicate node not inserted, return directly\n  updateHeight(node); // Update node height\n  /* 2. Perform rotation operation to restore balance to this subtree */\n  node = rotate(node);\n  // Return root node of subtree\n  return node;\n}\n
avl_tree.rs
/* Insert node */\nfn insert(&mut self, val: i32) {\n    self.root = Self::insert_helper(self.root.clone(), val);\n}\n\n/* Recursively insert node (helper method) */\nfn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc {\n    match node {\n        Some(mut node) => {\n            /* 1. Find insertion position and insert node */\n            match {\n                let node_val = node.borrow().val;\n                node_val\n            }\n            .cmp(&val)\n            {\n                Ordering::Greater => {\n                    let left = node.borrow().left.clone();\n                    node.borrow_mut().left = Self::insert_helper(left, val);\n                }\n                Ordering::Less => {\n                    let right = node.borrow().right.clone();\n                    node.borrow_mut().right = Self::insert_helper(right, val);\n                }\n                Ordering::Equal => {\n                    return Some(node); // Duplicate node not inserted, return directly\n                }\n            }\n            Self::update_height(Some(node.clone())); // Update node height\n\n            /* 2. Perform rotation operation to restore balance to this subtree */\n            node = Self::rotate(Some(node)).unwrap();\n            // Return root node of subtree\n            Some(node)\n        }\n        None => Some(TreeNode::new(val)),\n    }\n}\n
avl_tree.c
/* Insert node */\nvoid insert(AVLTree *tree, int val) {\n    tree->root = insertHelper(tree->root, val);\n}\n\n/* Recursively insert node (helper function) */\nTreeNode *insertHelper(TreeNode *node, int val) {\n    if (node == NULL) {\n        return newTreeNode(val);\n    }\n    /* 1. Find insertion position and insert node */\n    if (val < node->val) {\n        node->left = insertHelper(node->left, val);\n    } else if (val > node->val) {\n        node->right = insertHelper(node->right, val);\n    } else {\n        // Duplicate node not inserted, return directly\n        return node;\n    }\n    // Update node height\n    updateHeight(node);\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.kt
/* Insert node */\nfun insert(_val: Int) {\n    root = insertHelper(root, _val)\n}\n\n/* Recursively insert node (helper method) */\nfun insertHelper(n: TreeNode?, _val: Int): TreeNode {\n    if (n == null)\n        return TreeNode(_val)\n    var node = n\n    /* 1. Find insertion position and insert node */\n    if (_val < node._val)\n        node.left = insertHelper(node.left, _val)\n    else if (_val > node._val)\n        node.right = insertHelper(node.right, _val)\n    else\n        return node // Duplicate node not inserted, return directly\n    updateHeight(node) // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.rb
### Insert node ###\ndef insert(val)\n  @root = insert_helper(@root, val)\nend\n\n### Recursively insert node (helper method) ###\ndef insert_helper(node, val)\n  return TreeNode.new(val) if node.nil?\n  # 1. Find insertion position and insert node\n  if val < node.val\n    node.left = insert_helper(node.left, val)\n  elsif val > node.val\n    node.right = insert_helper(node.right, val)\n  else\n    # Duplicate node not inserted, return directly\n    return node\n  end\n  # Update node height\n  update_height(node)\n  # 2. Perform rotation operation to restore balance to this subtree\n  rotate(node)\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-node-removal","level":3,"title":"2.   Node Removal","text":"

Similarly, on the basis of the binary search tree's node removal method, rotation operations need to be performed from bottom to top to restore balance to all unbalanced nodes. The code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
def remove(self, val: int):\n    \"\"\"Delete node\"\"\"\n    self._root = self.remove_helper(self._root, val)\n\ndef remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None:\n    \"\"\"Recursively delete node (helper method)\"\"\"\n    if node is None:\n        return None\n    # 1. Find node and delete\n    if val < node.val:\n        node.left = self.remove_helper(node.left, val)\n    elif val > node.val:\n        node.right = self.remove_helper(node.right, val)\n    else:\n        if node.left is None or node.right is None:\n            child = node.left or node.right\n            # Number of child nodes = 0, delete node directly and return\n            if child is None:\n                return None\n            # Number of child nodes = 1, delete node directly\n            else:\n                node = child\n        else:\n            # Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            temp = node.right\n            while temp.left is not None:\n                temp = temp.left\n            node.right = self.remove_helper(node.right, temp.val)\n            node.val = temp.val\n    # Update node height\n    self.update_height(node)\n    # 2. Perform rotation operation to restore balance to this subtree\n    return self.rotate(node)\n
avl_tree.cpp
/* Remove node */\nvoid remove(int val) {\n    root = removeHelper(root, val);\n}\n\n/* Recursively delete node (helper method) */\nTreeNode *removeHelper(TreeNode *node, int val) {\n    if (node == nullptr)\n        return nullptr;\n    /* 1. Find node and delete */\n    if (val < node->val)\n        node->left = removeHelper(node->left, val);\n    else if (val > node->val)\n        node->right = removeHelper(node->right, val);\n    else {\n        if (node->left == nullptr || node->right == nullptr) {\n            TreeNode *child = node->left != nullptr ? node->left : node->right;\n            // Number of child nodes = 0, delete node directly and return\n            if (child == nullptr) {\n                delete node;\n                return nullptr;\n            }\n            // Number of child nodes = 1, delete node directly\n            else {\n                delete node;\n                node = child;\n            }\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            TreeNode *temp = node->right;\n            while (temp->left != nullptr) {\n                temp = temp->left;\n            }\n            int tempVal = temp->val;\n            node->right = removeHelper(node->right, temp->val);\n            node->val = tempVal;\n        }\n    }\n    updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.java
/* Remove node */\nvoid remove(int val) {\n    root = removeHelper(root, val);\n}\n\n/* Recursively delete node (helper method) */\nTreeNode removeHelper(TreeNode node, int val) {\n    if (node == null)\n        return null;\n    /* 1. Find node and delete */\n    if (val < node.val)\n        node.left = removeHelper(node.left, val);\n    else if (val > node.val)\n        node.right = removeHelper(node.right, val);\n    else {\n        if (node.left == null || node.right == null) {\n            TreeNode child = node.left != null ? node.left : node.right;\n            // Number of child nodes = 0, delete node directly and return\n            if (child == null)\n                return null;\n            // Number of child nodes = 1, delete node directly\n            else\n                node = child;\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            TreeNode temp = node.right;\n            while (temp.left != null) {\n                temp = temp.left;\n            }\n            node.right = removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.cs
/* Remove node */\nvoid Remove(int val) {\n    root = RemoveHelper(root, val);\n}\n\n/* Recursively delete node (helper method) */\nTreeNode? RemoveHelper(TreeNode? node, int val) {\n    if (node == null) return null;\n    /* 1. Find node and delete */\n    if (val < node.val)\n        node.left = RemoveHelper(node.left, val);\n    else if (val > node.val)\n        node.right = RemoveHelper(node.right, val);\n    else {\n        if (node.left == null || node.right == null) {\n            TreeNode? child = node.left ?? node.right;\n            // Number of child nodes = 0, delete node directly and return\n            if (child == null)\n                return null;\n            // Number of child nodes = 1, delete node directly\n            else\n                node = child;\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            TreeNode? temp = node.right;\n            while (temp.left != null) {\n                temp = temp.left;\n            }\n            node.right = RemoveHelper(node.right, temp.val!.Value);\n            node.val = temp.val;\n        }\n    }\n    UpdateHeight(node);  // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = Rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.go
/* Remove node */\nfunc (t *aVLTree) remove(val int) {\n    t.root = t.removeHelper(t.root, val)\n}\n\n/* Recursively remove node (helper function) */\nfunc (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode {\n    if node == nil {\n        return nil\n    }\n    /* 1. Find node and delete */\n    if val < node.Val.(int) {\n        node.Left = t.removeHelper(node.Left, val)\n    } else if val > node.Val.(int) {\n        node.Right = t.removeHelper(node.Right, val)\n    } else {\n        if node.Left == nil || node.Right == nil {\n            child := node.Left\n            if node.Right != nil {\n                child = node.Right\n            }\n            if child == nil {\n                // Number of child nodes = 0, delete node directly and return\n                return nil\n            } else {\n                // Number of child nodes = 1, delete node directly\n                node = child\n            }\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            temp := node.Right\n            for temp.Left != nil {\n                temp = temp.Left\n            }\n            node.Right = t.removeHelper(node.Right, temp.Val.(int))\n            node.Val = temp.Val\n        }\n    }\n    // Update node height\n    t.updateHeight(node)\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = t.rotate(node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.swift
/* Remove node */\nfunc remove(val: Int) {\n    root = removeHelper(node: root, val: val)\n}\n\n/* Recursively delete node (helper method) */\nfunc removeHelper(node: TreeNode?, val: Int) -> TreeNode? {\n    var node = node\n    if node == nil {\n        return nil\n    }\n    /* 1. Find node and delete */\n    if val < node!.val {\n        node?.left = removeHelper(node: node?.left, val: val)\n    } else if val > node!.val {\n        node?.right = removeHelper(node: node?.right, val: val)\n    } else {\n        if node?.left == nil || node?.right == nil {\n            let child = node?.left ?? node?.right\n            // Number of child nodes = 0, delete node directly and return\n            if child == nil {\n                return nil\n            }\n            // Number of child nodes = 1, delete node directly\n            else {\n                node = child\n            }\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            var temp = node?.right\n            while temp?.left != nil {\n                temp = temp?.left\n            }\n            node?.right = removeHelper(node: node?.right, val: temp!.val)\n            node?.val = temp!.val\n        }\n    }\n    updateHeight(node: node) // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node: node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.js
/* Remove node */\nremove(val) {\n    this.root = this.#removeHelper(this.root, val);\n}\n\n/* Recursively delete node (helper method) */\n#removeHelper(node, val) {\n    if (node === null) return null;\n    /* 1. Find node and delete */\n    if (val < node.val) node.left = this.#removeHelper(node.left, val);\n    else if (val > node.val)\n        node.right = this.#removeHelper(node.right, val);\n    else {\n        if (node.left === null || node.right === null) {\n            const child = node.left !== null ? node.left : node.right;\n            // Number of child nodes = 0, delete node directly and return\n            if (child === null) return null;\n            // Number of child nodes = 1, delete node directly\n            else node = child;\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            let temp = node.right;\n            while (temp.left !== null) {\n                temp = temp.left;\n            }\n            node.right = this.#removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    this.#updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = this.#rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.ts
/* Remove node */\nremove(val: number): void {\n    this.root = this.removeHelper(this.root, val);\n}\n\n/* Recursively delete node (helper method) */\nremoveHelper(node: TreeNode, val: number): TreeNode {\n    if (node === null) return null;\n    /* 1. Find node and delete */\n    if (val < node.val) {\n        node.left = this.removeHelper(node.left, val);\n    } else if (val > node.val) {\n        node.right = this.removeHelper(node.right, val);\n    } else {\n        if (node.left === null || node.right === null) {\n            const child = node.left !== null ? node.left : node.right;\n            // Number of child nodes = 0, delete node directly and return\n            if (child === null) {\n                return null;\n            } else {\n                // Number of child nodes = 1, delete node directly\n                node = child;\n            }\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            let temp = node.right;\n            while (temp.left !== null) {\n                temp = temp.left;\n            }\n            node.right = this.removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    this.updateHeight(node); // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = this.rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.dart
/* Remove node */\nvoid remove(int val) {\n  root = removeHelper(root, val);\n}\n\n/* Recursively delete node (helper method) */\nTreeNode? removeHelper(TreeNode? node, int val) {\n  if (node == null) return null;\n  /* 1. Find node and delete */\n  if (val < node.val)\n    node.left = removeHelper(node.left, val);\n  else if (val > node.val)\n    node.right = removeHelper(node.right, val);\n  else {\n    if (node.left == null || node.right == null) {\n      TreeNode? child = node.left ?? node.right;\n      // Number of child nodes = 0, delete node directly and return\n      if (child == null)\n        return null;\n      // Number of child nodes = 1, delete node directly\n      else\n        node = child;\n    } else {\n      // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n      TreeNode? temp = node.right;\n      while (temp!.left != null) {\n        temp = temp.left;\n      }\n      node.right = removeHelper(node.right, temp.val);\n      node.val = temp.val;\n    }\n  }\n  updateHeight(node); // Update node height\n  /* 2. Perform rotation operation to restore balance to this subtree */\n  node = rotate(node);\n  // Return root node of subtree\n  return node;\n}\n
avl_tree.rs
/* Remove node */\nfn remove(&self, val: i32) {\n    Self::remove_helper(self.root.clone(), val);\n}\n\n/* Recursively delete node (helper method) */\nfn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc {\n    match node {\n        Some(mut node) => {\n            /* 1. Find node and delete */\n            if val < node.borrow().val {\n                let left = node.borrow().left.clone();\n                node.borrow_mut().left = Self::remove_helper(left, val);\n            } else if val > node.borrow().val {\n                let right = node.borrow().right.clone();\n                node.borrow_mut().right = Self::remove_helper(right, val);\n            } else if node.borrow().left.is_none() || node.borrow().right.is_none() {\n                let child = if node.borrow().left.is_some() {\n                    node.borrow().left.clone()\n                } else {\n                    node.borrow().right.clone()\n                };\n                match child {\n                    // Number of child nodes = 0, delete node directly and return\n                    None => {\n                        return None;\n                    }\n                    // Number of child nodes = 1, delete node directly\n                    Some(child) => node = child,\n                }\n            } else {\n                // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n                let mut temp = node.borrow().right.clone().unwrap();\n                loop {\n                    let temp_left = temp.borrow().left.clone();\n                    if temp_left.is_none() {\n                        break;\n                    }\n                    temp = temp_left.unwrap();\n                }\n                let right = node.borrow().right.clone();\n                node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val);\n                node.borrow_mut().val = temp.borrow().val;\n            }\n            Self::update_height(Some(node.clone())); // Update node height\n\n            /* 2. Perform rotation operation to restore balance to this subtree */\n            node = Self::rotate(Some(node)).unwrap();\n            // Return root node of subtree\n            Some(node)\n        }\n        None => None,\n    }\n}\n
avl_tree.c
/* Remove node */\n// Cannot use remove keyword here due to stdio.h inclusion\nvoid removeItem(AVLTree *tree, int val) {\n    TreeNode *root = removeHelper(tree->root, val);\n}\n\n/* Recursively remove node (helper function) */\nTreeNode *removeHelper(TreeNode *node, int val) {\n    TreeNode *child, *grandChild;\n    if (node == NULL) {\n        return NULL;\n    }\n    /* 1. Find node and delete */\n    if (val < node->val) {\n        node->left = removeHelper(node->left, val);\n    } else if (val > node->val) {\n        node->right = removeHelper(node->right, val);\n    } else {\n        if (node->left == NULL || node->right == NULL) {\n            child = node->left;\n            if (node->right != NULL) {\n                child = node->right;\n            }\n            // Number of child nodes = 0, delete node directly and return\n            if (child == NULL) {\n                return NULL;\n            } else {\n                // Number of child nodes = 1, delete node directly\n                node = child;\n            }\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            TreeNode *temp = node->right;\n            while (temp->left != NULL) {\n                temp = temp->left;\n            }\n            int tempVal = temp->val;\n            node->right = removeHelper(node->right, temp->val);\n            node->val = tempVal;\n        }\n    }\n    // Update node height\n    updateHeight(node);\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node);\n    // Return root node of subtree\n    return node;\n}\n
avl_tree.kt
/* Remove node */\nfun remove(_val: Int) {\n    root = removeHelper(root, _val)\n}\n\n/* Recursively delete node (helper method) */\nfun removeHelper(n: TreeNode?, _val: Int): TreeNode? {\n    var node = n ?: return null\n    /* 1. Find node and delete */\n    if (_val < node._val)\n        node.left = removeHelper(node.left, _val)\n    else if (_val > node._val)\n        node.right = removeHelper(node.right, _val)\n    else {\n        if (node.left == null || node.right == null) {\n            val child = if (node.left != null)\n                node.left\n            else\n                node.right\n            // Number of child nodes = 0, delete node directly and return\n            if (child == null)\n                return null\n            // Number of child nodes = 1, delete node directly\n            else\n                node = child\n        } else {\n            // Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n            var temp = node.right\n            while (temp!!.left != null) {\n                temp = temp.left\n            }\n            node.right = removeHelper(node.right, temp._val)\n            node._val = temp._val\n        }\n    }\n    updateHeight(node) // Update node height\n    /* 2. Perform rotation operation to restore balance to this subtree */\n    node = rotate(node)\n    // Return root node of subtree\n    return node\n}\n
avl_tree.rb
### Delete node ###\ndef remove(val)\n  @root = remove_helper(@root, val)\nend\n\n### Recursively delete node (helper method) ###\ndef remove_helper(node, val)\n  return if node.nil?\n  # 1. Find node and delete\n  if val < node.val\n    node.left = remove_helper(node.left, val)\n  elsif val > node.val\n    node.right = remove_helper(node.right, val)\n  else\n    if node.left.nil? || node.right.nil?\n      child = node.left || node.right\n      # Number of child nodes = 0, delete node directly and return\n      return if child.nil?\n      # Number of child nodes = 1, delete node directly\n      node = child\n    else\n      # Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it\n      temp = node.right\n      while !temp.left.nil?\n        temp = temp.left\n      end\n      node.right = remove_helper(node.right, temp.val)\n      node.val = temp.val\n    end\n  end\n  # Update node height\n  update_height(node)\n  # 2. Perform rotation operation to restore balance to this subtree\n  rotate(node)\nend\n
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#3-node-search","level":3,"title":"3.   Node Search","text":"

The node search operation in AVL trees is consistent with that in binary search trees, and will not be elaborated here.

","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/avl_tree/#754-typical-applications-of-avl-trees","level":2,"title":"7.5.4   Typical Applications of Avl Trees","text":"
  • Organizing and storing large-scale data, suitable for scenarios with high-frequency searches and low-frequency insertions and deletions.
  • Used to build index systems in databases.
  • Red-black trees are also a common type of balanced binary search tree. Compared to AVL trees, red-black trees have more relaxed balance conditions, require fewer rotation operations for node insertion and deletion, and have higher average efficiency for node addition and deletion operations.
","path":["Chapter 7. Tree","7.5   Avl Tree *"],"tags":[]},{"location":"chapter_tree/binary_search_tree/","level":1,"title":"7.4   Binary Search Tree","text":"

As shown in Figure 7-16, a binary search tree satisfies the following conditions.

  1. For the root node, the value of all nodes in the left subtree \\(<\\) the value of the root node \\(<\\) the value of all nodes in the right subtree.
  2. The left and right subtrees of any node are also binary search trees, i.e., they satisfy condition 1. as well.

Figure 7-16   Binary search tree

","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#741-operations-on-a-binary-search-tree","level":2,"title":"7.4.1   Operations on a Binary Search Tree","text":"

We encapsulate the binary search tree as a class BinarySearchTree and declare a member variable root pointing to the tree's root node.

","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#1-searching-for-a-node","level":3,"title":"1.   Searching for a Node","text":"

Given a target node value num, we can search according to the properties of the binary search tree. As shown in Figure 7-17, we declare a node cur and start from the binary tree's root node root, looping to compare the node value cur.val with num.

  • If cur.val < num, it means the target node is in cur's right subtree, thus execute cur = cur.right.
  • If cur.val > num, it means the target node is in cur's left subtree, thus execute cur = cur.left.
  • If cur.val = num, it means the target node is found, exit the loop, and return the node.
<1><2><3><4>

Figure 7-17   Example of searching for a node in a binary search tree

The search operation in a binary search tree works on the same principle as the binary search algorithm, both eliminating half of the cases in each round. The number of loop iterations is at most the height of the binary tree. When the binary tree is balanced, it uses \\(O(\\log n)\\) time. The example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
def search(self, num: int) -> TreeNode | None:\n    \"\"\"Search node\"\"\"\n    cur = self._root\n    # Loop search, exit after passing leaf node\n    while cur is not None:\n        # Target node is in cur's right subtree\n        if cur.val < num:\n            cur = cur.right\n        # Target node is in cur's left subtree\n        elif cur.val > num:\n            cur = cur.left\n        # Found target node, exit loop\n        else:\n            break\n    return cur\n
binary_search_tree.cpp
/* Search node */\nTreeNode *search(int num) {\n    TreeNode *cur = root;\n    // Loop search, exit after passing leaf node\n    while (cur != nullptr) {\n        // Target node is in cur's right subtree\n        if (cur->val < num)\n            cur = cur->right;\n        // Target node is in cur's left subtree\n        else if (cur->val > num)\n            cur = cur->left;\n        // Found target node, exit loop\n        else\n            break;\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.java
/* Search node */\nTreeNode search(int num) {\n    TreeNode cur = root;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Target node is in cur's right subtree\n        if (cur.val < num)\n            cur = cur.right;\n        // Target node is in cur's left subtree\n        else if (cur.val > num)\n            cur = cur.left;\n        // Found target node, exit loop\n        else\n            break;\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.cs
/* Search node */\nTreeNode? Search(int num) {\n    TreeNode? cur = root;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Target node is in cur's right subtree\n        if (cur.val < num) cur =\n            cur.right;\n        // Target node is in cur's left subtree\n        else if (cur.val > num)\n            cur = cur.left;\n        // Found target node, exit loop\n        else\n            break;\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.go
/* Search node */\nfunc (bst *binarySearchTree) search(num int) *TreeNode {\n    node := bst.root\n    // Loop search, exit after passing leaf node\n    for node != nil {\n        if node.Val.(int) < num {\n            // Target node is in cur's right subtree\n            node = node.Right\n        } else if node.Val.(int) > num {\n            // Target node is in cur's left subtree\n            node = node.Left\n        } else {\n            // Found target node, exit loop\n            break\n        }\n    }\n    // Return target node\n    return node\n}\n
binary_search_tree.swift
/* Search node */\nfunc search(num: Int) -> TreeNode? {\n    var cur = root\n    // Loop search, exit after passing leaf node\n    while cur != nil {\n        // Target node is in cur's right subtree\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // Target node is in cur's left subtree\n        else if cur!.val > num {\n            cur = cur?.left\n        }\n        // Found target node, exit loop\n        else {\n            break\n        }\n    }\n    // Return target node\n    return cur\n}\n
binary_search_tree.js
/* Search node */\nsearch(num) {\n    let cur = this.root;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Target node is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Target node is in cur's left subtree\n        else if (cur.val > num) cur = cur.left;\n        // Found target node, exit loop\n        else break;\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.ts
/* Search node */\nsearch(num: number): TreeNode | null {\n    let cur = this.root;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Target node is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Target node is in cur's left subtree\n        else if (cur.val > num) cur = cur.left;\n        // Found target node, exit loop\n        else break;\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.dart
/* Search node */\nTreeNode? search(int _num) {\n  TreeNode? cur = _root;\n  // Loop search, exit after passing leaf node\n  while (cur != null) {\n    // Target node is in cur's right subtree\n    if (cur.val < _num)\n      cur = cur.right;\n    // Target node is in cur's left subtree\n    else if (cur.val > _num)\n      cur = cur.left;\n    // Found target node, exit loop\n    else\n      break;\n  }\n  // Return target node\n  return cur;\n}\n
binary_search_tree.rs
/* Search node */\npub fn search(&self, num: i32) -> OptionTreeNodeRc {\n    let mut cur = self.root.clone();\n    // Loop search, exit after passing leaf node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // Target node is in cur's right subtree\n            Ordering::Greater => cur = node.borrow().right.clone(),\n            // Target node is in cur's left subtree\n            Ordering::Less => cur = node.borrow().left.clone(),\n            // Found target node, exit loop\n            Ordering::Equal => break,\n        }\n    }\n\n    // Return target node\n    cur\n}\n
binary_search_tree.c
/* Search node */\nTreeNode *search(BinarySearchTree *bst, int num) {\n    TreeNode *cur = bst->root;\n    // Loop search, exit after passing leaf node\n    while (cur != NULL) {\n        if (cur->val < num) {\n            // Target node is in cur's right subtree\n            cur = cur->right;\n        } else if (cur->val > num) {\n            // Target node is in cur's left subtree\n            cur = cur->left;\n        } else {\n            // Found target node, exit loop\n            break;\n        }\n    }\n    // Return target node\n    return cur;\n}\n
binary_search_tree.kt
/* Search node */\nfun search(num: Int): TreeNode? {\n    var cur = root\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Target node is in cur's right subtree\n        cur = if (cur._val < num)\n            cur.right\n        // Target node is in cur's left subtree\n        else if (cur._val > num)\n            cur.left\n        // Found target node, exit loop\n        else\n            break\n    }\n    // Return target node\n    return cur\n}\n
binary_search_tree.rb
### Search node ###\ndef search(num)\n  cur = @root\n\n  # Loop search, exit after passing leaf node\n  while !cur.nil?\n    # Target node is in cur's right subtree\n    if cur.val < num\n      cur = cur.right\n    # Target node is in cur's left subtree\n    elsif cur.val > num\n      cur = cur.left\n    # Found target node, exit loop\n    else\n      break\n    end\n  end\n\n  cur\nend\n
","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#2-inserting-a-node","level":3,"title":"2.   Inserting a Node","text":"

Given an element num to be inserted, in order to maintain the property of the binary search tree \"left subtree < root node < right subtree,\" the insertion process is as shown in Figure 7-18.

  1. Finding the insertion position: Similar to the search operation, start from the root node and loop downward searching according to the size relationship between the current node value and num, until passing the leaf node (traversing to None) and then exit the loop.
  2. Insert the node at that position: Initialize node num and place it at the None position.

Figure 7-18   Inserting a node into a binary search tree

In the code implementation, note the following two points:

  • Binary search trees do not allow duplicate nodes; otherwise, it would violate its definition. Therefore, if the node to be inserted already exists in the tree, the insertion is not performed and it returns directly.
  • To implement the node insertion, we need to use node pre to save the node from the previous loop iteration. This way, when traversing to None, we can obtain its parent node, thereby completing the node insertion operation.
PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
def insert(self, num: int):\n    \"\"\"Insert node\"\"\"\n    # If tree is empty, initialize root node\n    if self._root is None:\n        self._root = TreeNode(num)\n        return\n    # Loop search, exit after passing leaf node\n    cur, pre = self._root, None\n    while cur is not None:\n        # Found duplicate node, return directly\n        if cur.val == num:\n            return\n        pre = cur\n        # Insertion position is in cur's right subtree\n        if cur.val < num:\n            cur = cur.right\n        # Insertion position is in cur's left subtree\n        else:\n            cur = cur.left\n    # Insert node\n    node = TreeNode(num)\n    if pre.val < num:\n        pre.right = node\n    else:\n        pre.left = node\n
binary_search_tree.cpp
/* Insert node */\nvoid insert(int num) {\n    // If tree is empty, initialize root node\n    if (root == nullptr) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode *cur = root, *pre = nullptr;\n    // Loop search, exit after passing leaf node\n    while (cur != nullptr) {\n        // Found duplicate node, return directly\n        if (cur->val == num)\n            return;\n        pre = cur;\n        // Insertion position is in cur's right subtree\n        if (cur->val < num)\n            cur = cur->right;\n        // Insertion position is in cur's left subtree\n        else\n            cur = cur->left;\n    }\n    // Insert node\n    TreeNode *node = new TreeNode(num);\n    if (pre->val < num)\n        pre->right = node;\n    else\n        pre->left = node;\n}\n
binary_search_tree.java
/* Insert node */\nvoid insert(int num) {\n    // If tree is empty, initialize root node\n    if (root == null) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode cur = root, pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found duplicate node, return directly\n        if (cur.val == num)\n            return;\n        pre = cur;\n        // Insertion position is in cur's right subtree\n        if (cur.val < num)\n            cur = cur.right;\n        // Insertion position is in cur's left subtree\n        else\n            cur = cur.left;\n    }\n    // Insert node\n    TreeNode node = new TreeNode(num);\n    if (pre.val < num)\n        pre.right = node;\n    else\n        pre.left = node;\n}\n
binary_search_tree.cs
/* Insert node */\nvoid Insert(int num) {\n    // If tree is empty, initialize root node\n    if (root == null) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode? cur = root, pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found duplicate node, return directly\n        if (cur.val == num)\n            return;\n        pre = cur;\n        // Insertion position is in cur's right subtree\n        if (cur.val < num)\n            cur = cur.right;\n        // Insertion position is in cur's left subtree\n        else\n            cur = cur.left;\n    }\n\n    // Insert node\n    TreeNode node = new(num);\n    if (pre != null) {\n        if (pre.val < num)\n            pre.right = node;\n        else\n            pre.left = node;\n    }\n}\n
binary_search_tree.go
/* Insert node */\nfunc (bst *binarySearchTree) insert(num int) {\n    cur := bst.root\n    // If tree is empty, initialize root node\n    if cur == nil {\n        bst.root = NewTreeNode(num)\n        return\n    }\n    // Node position before the node to be inserted\n    var pre *TreeNode = nil\n    // Loop search, exit after passing leaf node\n    for cur != nil {\n        if cur.Val == num {\n            return\n        }\n        pre = cur\n        if cur.Val.(int) < num {\n            cur = cur.Right\n        } else {\n            cur = cur.Left\n        }\n    }\n    // Insert node\n    node := NewTreeNode(num)\n    if pre.Val.(int) < num {\n        pre.Right = node\n    } else {\n        pre.Left = node\n    }\n}\n
binary_search_tree.swift
/* Insert node */\nfunc insert(num: Int) {\n    // If tree is empty, initialize root node\n    if root == nil {\n        root = TreeNode(x: num)\n        return\n    }\n    var cur = root\n    var pre: TreeNode?\n    // Loop search, exit after passing leaf node\n    while cur != nil {\n        // Found duplicate node, return directly\n        if cur!.val == num {\n            return\n        }\n        pre = cur\n        // Insertion position is in cur's right subtree\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // Insertion position is in cur's left subtree\n        else {\n            cur = cur?.left\n        }\n    }\n    // Insert node\n    let node = TreeNode(x: num)\n    if pre!.val < num {\n        pre?.right = node\n    } else {\n        pre?.left = node\n    }\n}\n
binary_search_tree.js
/* Insert node */\ninsert(num) {\n    // If tree is empty, initialize root node\n    if (this.root === null) {\n        this.root = new TreeNode(num);\n        return;\n    }\n    let cur = this.root,\n        pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Found duplicate node, return directly\n        if (cur.val === num) return;\n        pre = cur;\n        // Insertion position is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Insertion position is in cur's left subtree\n        else cur = cur.left;\n    }\n    // Insert node\n    const node = new TreeNode(num);\n    if (pre.val < num) pre.right = node;\n    else pre.left = node;\n}\n
binary_search_tree.ts
/* Insert node */\ninsert(num: number): void {\n    // If tree is empty, initialize root node\n    if (this.root === null) {\n        this.root = new TreeNode(num);\n        return;\n    }\n    let cur: TreeNode | null = this.root,\n        pre: TreeNode | null = null;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Found duplicate node, return directly\n        if (cur.val === num) return;\n        pre = cur;\n        // Insertion position is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Insertion position is in cur's left subtree\n        else cur = cur.left;\n    }\n    // Insert node\n    const node = new TreeNode(num);\n    if (pre!.val < num) pre!.right = node;\n    else pre!.left = node;\n}\n
binary_search_tree.dart
/* Insert node */\nvoid insert(int _num) {\n  // If tree is empty, initialize root node\n  if (_root == null) {\n    _root = TreeNode(_num);\n    return;\n  }\n  TreeNode? cur = _root;\n  TreeNode? pre = null;\n  // Loop search, exit after passing leaf node\n  while (cur != null) {\n    // Found duplicate node, return directly\n    if (cur.val == _num) return;\n    pre = cur;\n    // Insertion position is in cur's right subtree\n    if (cur.val < _num)\n      cur = cur.right;\n    // Insertion position is in cur's left subtree\n    else\n      cur = cur.left;\n  }\n  // Insert node\n  TreeNode? node = TreeNode(_num);\n  if (pre!.val < _num)\n    pre.right = node;\n  else\n    pre.left = node;\n}\n
binary_search_tree.rs
/* Insert node */\npub fn insert(&mut self, num: i32) {\n    // If tree is empty, initialize root node\n    if self.root.is_none() {\n        self.root = Some(TreeNode::new(num));\n        return;\n    }\n    let mut cur = self.root.clone();\n    let mut pre = None;\n    // Loop search, exit after passing leaf node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // Found duplicate node, return directly\n            Ordering::Equal => return,\n            // Insertion position is in cur's right subtree\n            Ordering::Greater => {\n                pre = cur.clone();\n                cur = node.borrow().right.clone();\n            }\n            // Insertion position is in cur's left subtree\n            Ordering::Less => {\n                pre = cur.clone();\n                cur = node.borrow().left.clone();\n            }\n        }\n    }\n    // Insert node\n    let pre = pre.unwrap();\n    let node = Some(TreeNode::new(num));\n    if num > pre.borrow().val {\n        pre.borrow_mut().right = node;\n    } else {\n        pre.borrow_mut().left = node;\n    }\n}\n
binary_search_tree.c
/* Insert node */\nvoid insert(BinarySearchTree *bst, int num) {\n    // If tree is empty, initialize root node\n    if (bst->root == NULL) {\n        bst->root = newTreeNode(num);\n        return;\n    }\n    TreeNode *cur = bst->root, *pre = NULL;\n    // Loop search, exit after passing leaf node\n    while (cur != NULL) {\n        // Found duplicate node, return directly\n        if (cur->val == num) {\n            return;\n        }\n        pre = cur;\n        if (cur->val < num) {\n            // Insertion position is in cur's right subtree\n            cur = cur->right;\n        } else {\n            // Insertion position is in cur's left subtree\n            cur = cur->left;\n        }\n    }\n    // Insert node\n    TreeNode *node = newTreeNode(num);\n    if (pre->val < num) {\n        pre->right = node;\n    } else {\n        pre->left = node;\n    }\n}\n
binary_search_tree.kt
/* Insert node */\nfun insert(num: Int) {\n    // If tree is empty, initialize root node\n    if (root == null) {\n        root = TreeNode(num)\n        return\n    }\n    var cur = root\n    var pre: TreeNode? = null\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found duplicate node, return directly\n        if (cur._val == num)\n            return\n        pre = cur\n        // Insertion position is in cur's right subtree\n        cur = if (cur._val < num)\n            cur.right\n        // Insertion position is in cur's left subtree\n        else\n            cur.left\n    }\n    // Insert node\n    val node = TreeNode(num)\n    if (pre?._val!! < num)\n        pre.right = node\n    else\n        pre.left = node\n}\n
binary_search_tree.rb
### Insert node ###\ndef insert(num)\n  # If tree is empty, initialize root node\n  if @root.nil?\n    @root = TreeNode.new(num)\n    return\n  end\n\n  # Loop search, exit after passing leaf node\n  cur, pre = @root, nil\n  while !cur.nil?\n    # Found duplicate node, return directly\n    return if cur.val == num\n\n    pre = cur\n    # Insertion position is in cur's right subtree\n    if cur.val < num\n      cur = cur.right\n    # Insertion position is in cur's left subtree\n    else\n      cur = cur.left\n    end\n  end\n\n  # Insert node\n  node = TreeNode.new(num)\n  if pre.val < num\n    pre.right = node\n  else\n    pre.left = node\n  end\nend\n

Similar to searching for a node, inserting a node uses \\(O(\\log n)\\) time.

","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#3-removing-a-node","level":3,"title":"3.   Removing a Node","text":"

First, find the target node in the binary tree, then remove it. Similar to node insertion, we need to ensure that after the removal operation is completed, the binary search tree's property of \"left subtree \\(<\\) root node \\(<\\) right subtree\" is still maintained. Therefore, depending on the number of child nodes the target node has, we divide it into 0, 1, and 2 three cases, and execute the corresponding node removal operations.

As shown in Figure 7-19, when the degree of the node to be removed is \\(0\\), it means the node is a leaf node and can be directly removed.

Figure 7-19   Removing a node in a binary search tree (degree 0)

As shown in Figure 7-20, when the degree of the node to be removed is \\(1\\), replacing the node to be removed with its child node is sufficient.

Figure 7-20   Removing a node in a binary search tree (degree 1)

When the degree of the node to be removed is \\(2\\), we cannot directly remove it; instead, we need to use a node to replace it. To maintain the binary search tree's property of \"left subtree \\(<\\) root node \\(<\\) right subtree,\" this node can be either the smallest node in the right subtree or the largest node in the left subtree.

Assuming we choose the smallest node in the right subtree (the next node in the inorder traversal), the removal process is as shown in Figure 7-21.

  1. Find the next node of the node to be removed in the \"inorder traversal sequence,\" denoted as tmp.
  2. Replace the value of the node to be removed with the value of tmp, and recursively remove node tmp in the tree.
<1><2><3><4>

Figure 7-21   Removing a node in a binary search tree (degree 2)

The node removal operation also uses \\(O(\\log n)\\) time, where finding the node to be removed requires \\(O(\\log n)\\) time, and obtaining the inorder successor node requires \\(O(\\log n)\\) time. Example code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
def remove(self, num: int):\n    \"\"\"Delete node\"\"\"\n    # If tree is empty, return directly\n    if self._root is None:\n        return\n    # Loop search, exit after passing leaf node\n    cur, pre = self._root, None\n    while cur is not None:\n        # Found node to delete, exit loop\n        if cur.val == num:\n            break\n        pre = cur\n        # Node to delete is in cur's right subtree\n        if cur.val < num:\n            cur = cur.right\n        # Node to delete is in cur's left subtree\n        else:\n            cur = cur.left\n    # If no node to delete, return directly\n    if cur is None:\n        return\n\n    # Number of child nodes = 0 or 1\n    if cur.left is None or cur.right is None:\n        # When number of child nodes = 0 / 1, child = null / that child node\n        child = cur.left or cur.right\n        # Delete node cur\n        if cur != self._root:\n            if pre.left == cur:\n                pre.left = child\n            else:\n                pre.right = child\n        else:\n            # If deleted node is root node, reassign root node\n            self._root = child\n    # Number of child nodes = 2\n    else:\n        # Get next node of cur in inorder traversal\n        tmp: TreeNode = cur.right\n        while tmp.left is not None:\n            tmp = tmp.left\n        # Recursively delete node tmp\n        self.remove(tmp.val)\n        # Replace cur with tmp\n        cur.val = tmp.val\n
binary_search_tree.cpp
/* Remove node */\nvoid remove(int num) {\n    // If tree is empty, return directly\n    if (root == nullptr)\n        return;\n    TreeNode *cur = root, *pre = nullptr;\n    // Loop search, exit after passing leaf node\n    while (cur != nullptr) {\n        // Found node to delete, exit loop\n        if (cur->val == num)\n            break;\n        pre = cur;\n        // Node to delete is in cur's right subtree\n        if (cur->val < num)\n            cur = cur->right;\n        // Node to delete is in cur's left subtree\n        else\n            cur = cur->left;\n    }\n    // If no node to delete, return directly\n    if (cur == nullptr)\n        return;\n    // Number of child nodes = 0 or 1\n    if (cur->left == nullptr || cur->right == nullptr) {\n        // When number of child nodes = 0 / 1, child = nullptr / that child node\n        TreeNode *child = cur->left != nullptr ? cur->left : cur->right;\n        // Delete node cur\n        if (cur != root) {\n            if (pre->left == cur)\n                pre->left = child;\n            else\n                pre->right = child;\n        } else {\n            // If deleted node is root node, reassign root node\n            root = child;\n        }\n        // Free memory\n        delete cur;\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        TreeNode *tmp = cur->right;\n        while (tmp->left != nullptr) {\n            tmp = tmp->left;\n        }\n        int tmpVal = tmp->val;\n        // Recursively delete node tmp\n        remove(tmp->val);\n        // Replace cur with tmp\n        cur->val = tmpVal;\n    }\n}\n
binary_search_tree.java
/* Remove node */\nvoid remove(int num) {\n    // If tree is empty, return directly\n    if (root == null)\n        return;\n    TreeNode cur = root, pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found node to delete, exit loop\n        if (cur.val == num)\n            break;\n        pre = cur;\n        // Node to delete is in cur's right subtree\n        if (cur.val < num)\n            cur = cur.right;\n        // Node to delete is in cur's left subtree\n        else\n            cur = cur.left;\n    }\n    // If no node to delete, return directly\n    if (cur == null)\n        return;\n    // Number of child nodes = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        TreeNode child = cur.left != null ? cur.left : cur.right;\n        // Delete node cur\n        if (cur != root) {\n            if (pre.left == cur)\n                pre.left = child;\n            else\n                pre.right = child;\n        } else {\n            // If deleted node is root node, reassign root node\n            root = child;\n        }\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        TreeNode tmp = cur.right;\n        while (tmp.left != null) {\n            tmp = tmp.left;\n        }\n        // Recursively delete node tmp\n        remove(tmp.val);\n        // Replace cur with tmp\n        cur.val = tmp.val;\n    }\n}\n
binary_search_tree.cs
/* Remove node */\nvoid Remove(int num) {\n    // If tree is empty, return directly\n    if (root == null)\n        return;\n    TreeNode? cur = root, pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found node to delete, exit loop\n        if (cur.val == num)\n            break;\n        pre = cur;\n        // Node to delete is in cur's right subtree\n        if (cur.val < num)\n            cur = cur.right;\n        // Node to delete is in cur's left subtree\n        else\n            cur = cur.left;\n    }\n    // If no node to delete, return directly\n    if (cur == null)\n        return;\n    // Number of child nodes = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        TreeNode? child = cur.left ?? cur.right;\n        // Delete node cur\n        if (cur != root) {\n            if (pre!.left == cur)\n                pre.left = child;\n            else\n                pre.right = child;\n        } else {\n            // If deleted node is root node, reassign root node\n            root = child;\n        }\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        TreeNode? tmp = cur.right;\n        while (tmp.left != null) {\n            tmp = tmp.left;\n        }\n        // Recursively delete node tmp\n        Remove(tmp.val!.Value);\n        // Replace cur with tmp\n        cur.val = tmp.val;\n    }\n}\n
binary_search_tree.go
/* Remove node */\nfunc (bst *binarySearchTree) remove(num int) {\n    cur := bst.root\n    // If tree is empty, return directly\n    if cur == nil {\n        return\n    }\n    // Node position before the node to be removed\n    var pre *TreeNode = nil\n    // Loop search, exit after passing leaf node\n    for cur != nil {\n        if cur.Val == num {\n            break\n        }\n        pre = cur\n        if cur.Val.(int) < num {\n            // Node to be removed is in right subtree\n            cur = cur.Right\n        } else {\n            // Node to be removed is in left subtree\n            cur = cur.Left\n        }\n    }\n    // If no node to delete, return directly\n    if cur == nil {\n        return\n    }\n    // Number of child nodes is 0 or 1\n    if cur.Left == nil || cur.Right == nil {\n        var child *TreeNode = nil\n        // Get child node of node to be removed\n        if cur.Left != nil {\n            child = cur.Left\n        } else {\n            child = cur.Right\n        }\n        // Delete node cur\n        if cur != bst.root {\n            if pre.Left == cur {\n                pre.Left = child\n            } else {\n                pre.Right = child\n            }\n        } else {\n            // If deleted node is root node, reassign root node\n            bst.root = child\n        }\n        // Number of child nodes is 2\n    } else {\n        // Get next node of node cur to be removed in in-order traversal\n        tmp := cur.Right\n        for tmp.Left != nil {\n            tmp = tmp.Left\n        }\n        // Recursively delete node tmp\n        bst.remove(tmp.Val.(int))\n        // Replace cur with tmp\n        cur.Val = tmp.Val\n    }\n}\n
binary_search_tree.swift
/* Remove node */\nfunc remove(num: Int) {\n    // If tree is empty, return directly\n    if root == nil {\n        return\n    }\n    var cur = root\n    var pre: TreeNode?\n    // Loop search, exit after passing leaf node\n    while cur != nil {\n        // Found node to delete, exit loop\n        if cur!.val == num {\n            break\n        }\n        pre = cur\n        // Node to delete is in cur's right subtree\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // Node to delete is in cur's left subtree\n        else {\n            cur = cur?.left\n        }\n    }\n    // If no node to delete, return directly\n    if cur == nil {\n        return\n    }\n    // Number of child nodes = 0 or 1\n    if cur?.left == nil || cur?.right == nil {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        let child = cur?.left ?? cur?.right\n        // Delete node cur\n        if cur !== root {\n            if pre?.left === cur {\n                pre?.left = child\n            } else {\n                pre?.right = child\n            }\n        } else {\n            // If deleted node is root node, reassign root node\n            root = child\n        }\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        var tmp = cur?.right\n        while tmp?.left != nil {\n            tmp = tmp?.left\n        }\n        // Recursively delete node tmp\n        remove(num: tmp!.val)\n        // Replace cur with tmp\n        cur?.val = tmp!.val\n    }\n}\n
binary_search_tree.js
/* Remove node */\nremove(num) {\n    // If tree is empty, return directly\n    if (this.root === null) return;\n    let cur = this.root,\n        pre = null;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Found node to delete, exit loop\n        if (cur.val === num) break;\n        pre = cur;\n        // Node to delete is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Node to delete is in cur's left subtree\n        else cur = cur.left;\n    }\n    // If no node to delete, return directly\n    if (cur === null) return;\n    // Number of child nodes = 0 or 1\n    if (cur.left === null || cur.right === null) {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        const child = cur.left !== null ? cur.left : cur.right;\n        // Delete node cur\n        if (cur !== this.root) {\n            if (pre.left === cur) pre.left = child;\n            else pre.right = child;\n        } else {\n            // If deleted node is root node, reassign root node\n            this.root = child;\n        }\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        let tmp = cur.right;\n        while (tmp.left !== null) {\n            tmp = tmp.left;\n        }\n        // Recursively delete node tmp\n        this.remove(tmp.val);\n        // Replace cur with tmp\n        cur.val = tmp.val;\n    }\n}\n
binary_search_tree.ts
/* Remove node */\nremove(num: number): void {\n    // If tree is empty, return directly\n    if (this.root === null) return;\n    let cur: TreeNode | null = this.root,\n        pre: TreeNode | null = null;\n    // Loop search, exit after passing leaf node\n    while (cur !== null) {\n        // Found node to delete, exit loop\n        if (cur.val === num) break;\n        pre = cur;\n        // Node to delete is in cur's right subtree\n        if (cur.val < num) cur = cur.right;\n        // Node to delete is in cur's left subtree\n        else cur = cur.left;\n    }\n    // If no node to delete, return directly\n    if (cur === null) return;\n    // Number of child nodes = 0 or 1\n    if (cur.left === null || cur.right === null) {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        const child: TreeNode | null =\n            cur.left !== null ? cur.left : cur.right;\n        // Delete node cur\n        if (cur !== this.root) {\n            if (pre!.left === cur) pre!.left = child;\n            else pre!.right = child;\n        } else {\n            // If deleted node is root node, reassign root node\n            this.root = child;\n        }\n    }\n    // Number of child nodes = 2\n    else {\n        // Get next node of cur in inorder traversal\n        let tmp: TreeNode | null = cur.right;\n        while (tmp!.left !== null) {\n            tmp = tmp!.left;\n        }\n        // Recursively delete node tmp\n        this.remove(tmp!.val);\n        // Replace cur with tmp\n        cur.val = tmp!.val;\n    }\n}\n
binary_search_tree.dart
/* Remove node */\nvoid remove(int _num) {\n  // If tree is empty, return directly\n  if (_root == null) return;\n  TreeNode? cur = _root;\n  TreeNode? pre = null;\n  // Loop search, exit after passing leaf node\n  while (cur != null) {\n    // Found node to delete, exit loop\n    if (cur.val == _num) break;\n    pre = cur;\n    // Node to delete is in cur's right subtree\n    if (cur.val < _num)\n      cur = cur.right;\n    // Node to delete is in cur's left subtree\n    else\n      cur = cur.left;\n  }\n  // If no node to delete, return directly\n  if (cur == null) return;\n  // Number of child nodes = 0 or 1\n  if (cur.left == null || cur.right == null) {\n    // When number of child nodes = 0 / 1, child = null / that child node\n    TreeNode? child = cur.left ?? cur.right;\n    // Delete node cur\n    if (cur != _root) {\n      if (pre!.left == cur)\n        pre.left = child;\n      else\n        pre.right = child;\n    } else {\n      // If deleted node is root node, reassign root node\n      _root = child;\n    }\n  } else {\n    // Number of child nodes = 2\n    // Get next node of cur in inorder traversal\n    TreeNode? tmp = cur.right;\n    while (tmp!.left != null) {\n      tmp = tmp.left;\n    }\n    // Recursively delete node tmp\n    remove(tmp.val);\n    // Replace cur with tmp\n    cur.val = tmp.val;\n  }\n}\n
binary_search_tree.rs
/* Remove node */\npub fn remove(&mut self, num: i32) {\n    // If tree is empty, return directly\n    if self.root.is_none() {\n        return;\n    }\n    let mut cur = self.root.clone();\n    let mut pre = None;\n    // Loop search, exit after passing leaf node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // Found node to delete, exit loop\n            Ordering::Equal => break,\n            // Node to delete is in cur's right subtree\n            Ordering::Greater => {\n                pre = cur.clone();\n                cur = node.borrow().right.clone();\n            }\n            // Node to delete is in cur's left subtree\n            Ordering::Less => {\n                pre = cur.clone();\n                cur = node.borrow().left.clone();\n            }\n        }\n    }\n    // If no node to delete, return directly\n    if cur.is_none() {\n        return;\n    }\n    let cur = cur.unwrap();\n    let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone());\n    match (left_child.clone(), right_child.clone()) {\n        // Number of child nodes = 0 or 1\n        (None, None) | (Some(_), None) | (None, Some(_)) => {\n            // When number of child nodes = 0 / 1, child = nullptr / that child node\n            let child = left_child.or(right_child);\n            let pre = pre.unwrap();\n            // Delete node cur\n            if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) {\n                let left = pre.borrow().left.clone();\n                if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) {\n                    pre.borrow_mut().left = child;\n                } else {\n                    pre.borrow_mut().right = child;\n                }\n            } else {\n                // If deleted node is root node, reassign root node\n                self.root = child;\n            }\n        }\n        // Number of child nodes = 2\n        (Some(_), Some(_)) => {\n            // Get next node of cur in inorder traversal\n            let mut tmp = cur.borrow().right.clone();\n            while let Some(node) = tmp.clone() {\n                if node.borrow().left.is_some() {\n                    tmp = node.borrow().left.clone();\n                } else {\n                    break;\n                }\n            }\n            let tmp_val = tmp.unwrap().borrow().val;\n            // Recursively delete node tmp\n            self.remove(tmp_val);\n            // Replace cur with tmp\n            cur.borrow_mut().val = tmp_val;\n        }\n    }\n}\n
binary_search_tree.c
/* Remove node */\n// Cannot use remove keyword here due to stdio.h inclusion\nvoid removeItem(BinarySearchTree *bst, int num) {\n    // If tree is empty, return directly\n    if (bst->root == NULL)\n        return;\n    TreeNode *cur = bst->root, *pre = NULL;\n    // Loop search, exit after passing leaf node\n    while (cur != NULL) {\n        // Found node to delete, exit loop\n        if (cur->val == num)\n            break;\n        pre = cur;\n        if (cur->val < num) {\n            // Node to delete is in right subtree of root\n            cur = cur->right;\n        } else {\n            // Node to delete is in left subtree of root\n            cur = cur->left;\n        }\n    }\n    // If no node to delete, return directly\n    if (cur == NULL)\n        return;\n    // Check if node to delete has children\n    if (cur->left == NULL || cur->right == NULL) {\n        /* Number of child nodes = 0 or 1 */\n        // When number of child nodes = 0 / 1, child = nullptr / that child node\n        TreeNode *child = cur->left != NULL ? cur->left : cur->right;\n        // Delete node cur\n        if (pre->left == cur) {\n            pre->left = child;\n        } else {\n            pre->right = child;\n        }\n        // Free memory\n        free(cur);\n    } else {\n        /* Number of child nodes = 2 */\n        // Get next node of cur in inorder traversal\n        TreeNode *tmp = cur->right;\n        while (tmp->left != NULL) {\n            tmp = tmp->left;\n        }\n        int tmpVal = tmp->val;\n        // Recursively delete node tmp\n        removeItem(bst, tmp->val);\n        // Replace cur with tmp\n        cur->val = tmpVal;\n    }\n}\n
binary_search_tree.kt
/* Remove node */\nfun remove(num: Int) {\n    // If tree is empty, return directly\n    if (root == null)\n        return\n    var cur = root\n    var pre: TreeNode? = null\n    // Loop search, exit after passing leaf node\n    while (cur != null) {\n        // Found node to delete, exit loop\n        if (cur._val == num)\n            break\n        pre = cur\n        // Node to delete is in cur's right subtree\n        cur = if (cur._val < num)\n            cur.right\n        // Node to delete is in cur's left subtree\n        else\n            cur.left\n    }\n    // If no node to delete, return directly\n    if (cur == null)\n        return\n    // Number of child nodes = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // When number of child nodes = 0 / 1, child = null / that child node\n        val child = if (cur.left != null)\n            cur.left\n        else\n            cur.right\n        // Delete node cur\n        if (cur != root) {\n            if (pre!!.left == cur)\n                pre.left = child\n            else\n                pre.right = child\n        } else {\n            // If deleted node is root node, reassign root node\n            root = child\n        }\n        // Number of child nodes = 2\n    } else {\n        // Get next node of cur in inorder traversal\n        var tmp = cur.right\n        while (tmp!!.left != null) {\n            tmp = tmp.left\n        }\n        // Recursively delete node tmp\n        remove(tmp._val)\n        // Replace cur with tmp\n        cur._val = tmp._val\n    }\n}\n
binary_search_tree.rb
### Delete node ###\ndef remove(num)\n  # If tree is empty, return directly\n  return if @root.nil?\n\n  # Loop search, exit after passing leaf node\n  cur, pre = @root, nil\n  while !cur.nil?\n    # Found node to delete, exit loop\n    break if cur.val == num\n\n    pre = cur\n    # Node to delete is in cur's right subtree\n    if cur.val < num\n      cur = cur.right\n    # Node to delete is in cur's left subtree\n    else\n      cur = cur.left\n    end\n  end\n  # If no node to delete, return directly\n  return if cur.nil?\n\n  # Number of child nodes = 0 or 1\n  if cur.left.nil? || cur.right.nil?\n    # When number of child nodes = 0 / 1, child = null / that child node\n    child = cur.left || cur.right\n    # Delete node cur\n    if cur != @root\n      if pre.left == cur\n        pre.left = child\n      else\n        pre.right = child\n      end\n    else\n      # If deleted node is root node, reassign root node\n      @root = child\n    end\n  # Number of child nodes = 2\n  else\n    # Get next node of cur in inorder traversal\n    tmp = cur.right\n    while !tmp.left.nil?\n      tmp = tmp.left\n    end\n    # Recursively delete node tmp\n    remove(tmp.val)\n    # Replace cur with tmp\n    cur.val = tmp.val\n  end\nend\n
","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#4-inorder-traversal-is-ordered","level":3,"title":"4.   Inorder Traversal Is Ordered","text":"

As shown in Figure 7-22, the inorder traversal of a binary tree follows the \"left \\(\\rightarrow\\) root \\(\\rightarrow\\) right\" traversal order, while the binary search tree satisfies the \"left child node \\(<\\) root node \\(<\\) right child node\" size relationship.

This means that when performing an inorder traversal in a binary search tree, the next smallest node is always traversed first, thus yielding an important property: The inorder traversal sequence of a binary search tree is ascending.

Using the property of inorder traversal being ascending, we can obtain ordered data in a binary search tree in only \\(O(n)\\) time, without the need for additional sorting operations, which is very efficient.

Figure 7-22   Inorder traversal sequence of a binary search tree

","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#742-efficiency-of-binary-search-trees","level":2,"title":"7.4.2   Efficiency of Binary Search Trees","text":"

Given a set of data, we consider using an array or a binary search tree for storage. Observing Table 7-2, all operations in a binary search tree have logarithmic time complexity, providing stable and efficient performance. Arrays are more efficient than binary search trees only in scenarios with high-frequency additions and low-frequency searches and deletions.

Table 7-2   Efficiency comparison between arrays and search trees

Unsorted array Binary search tree Search element \\(O(n)\\) \\(O(\\log n)\\) Insert element \\(O(1)\\) \\(O(\\log n)\\) Remove element \\(O(n)\\) \\(O(\\log n)\\)

In the ideal case, a binary search tree is \"balanced,\" such that any node can be found within \\(\\log n\\) loop iterations.

However, if we continuously insert and remove nodes in a binary search tree, it may degenerate into a linked list as shown in Figure 7-23, where the time complexity of various operations also degrades to \\(O(n)\\).

Figure 7-23   Degradation of a binary search tree

","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#743-common-applications-of-binary-search-trees","level":2,"title":"7.4.3   Common Applications of Binary Search Trees","text":"
  • Used as multi-level indexes in systems to implement efficient search, insertion, and removal operations.
  • Serves as the underlying data structure for certain search algorithms.
  • Used to store data streams to maintain their ordered state.
","path":["Chapter 7. Tree","7.4   Binary Search Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/","level":1,"title":"7.1   Binary Tree","text":"

A binary tree is a non-linear data structure that represents the derivation relationship between \"ancestors\" and \"descendants\" and embodies the divide-and-conquer logic of \"one divides into two\". Similar to a linked list, the basic unit of a binary tree is a node, and each node contains a value, a reference to its left child node, and a reference to its right child node.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
class TreeNode:\n    \"\"\"Binary tree node\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                # Node value\n        self.left: TreeNode | None = None  # Reference to left child node\n        self.right: TreeNode | None = None # Reference to right child node\n
/* Binary tree node */\nstruct TreeNode {\n    int val;          // Node value\n    TreeNode *left;   // Pointer to left child node\n    TreeNode *right;  // Pointer to right child node\n    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}\n};\n
/* Binary tree node */\nclass TreeNode {\n    int val;         // Node value\n    TreeNode left;   // Reference to left child node\n    TreeNode right;  // Reference to right child node\n    TreeNode(int x) { val = x; }\n}\n
/* Binary tree node */\nclass TreeNode(int? x) {\n    public int? val = x;    // Node value\n    public TreeNode? left;  // Reference to left child node\n    public TreeNode? right; // Reference to right child node\n}\n
/* Binary tree node */\ntype TreeNode struct {\n    Val   int\n    Left  *TreeNode\n    Right *TreeNode\n}\n/* Constructor */\nfunc NewTreeNode(v int) *TreeNode {\n    return &TreeNode{\n        Left:  nil, // Pointer to left child node\n        Right: nil, // Pointer to right child node\n        Val:   v,   // Node value\n    }\n}\n
/* Binary tree node */\nclass TreeNode {\n    var val: Int // Node value\n    var left: TreeNode? // Reference to left child node\n    var right: TreeNode? // Reference to right child node\n\n    init(x: Int) {\n        val = x\n    }\n}\n
/* Binary tree node */\nclass TreeNode {\n    val; // Node value\n    left; // Pointer to left child node\n    right; // Pointer to right child node\n    constructor(val, left, right) {\n        this.val = val === undefined ? 0 : val;\n        this.left = left === undefined ? null : left;\n        this.right = right === undefined ? null : right;\n    }\n}\n
/* Binary tree node */\nclass TreeNode {\n    val: number;\n    left: TreeNode | null;\n    right: TreeNode | null;\n\n    constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.left = left === undefined ? null : left; // Reference to left child node\n        this.right = right === undefined ? null : right; // Reference to right child node\n    }\n}\n
/* Binary tree node */\nclass TreeNode {\n  int val;         // Node value\n  TreeNode? left;  // Reference to left child node\n  TreeNode? right; // Reference to right child node\n  TreeNode(this.val, [this.left, this.right]);\n}\n
use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Binary tree node */\nstruct TreeNode {\n    val: i32,                               // Node value\n    left: Option<Rc<RefCell<TreeNode>>>,    // Reference to left child node\n    right: Option<Rc<RefCell<TreeNode>>>,   // Reference to right child node\n}\n\nimpl TreeNode {\n    /* Constructor */\n    fn new(val: i32) -> Rc<RefCell<Self>> {\n        Rc::new(RefCell::new(Self {\n            val,\n            left: None,\n            right: None\n        }))\n    }\n}\n
/* Binary tree node */\ntypedef struct TreeNode {\n    int val;                // Node value\n    int height;             // Node height\n    struct TreeNode *left;  // Pointer to left child node\n    struct TreeNode *right; // Pointer to right child node\n} TreeNode;\n\n/* Constructor */\nTreeNode *newTreeNode(int val) {\n    TreeNode *node;\n\n    node = (TreeNode *)malloc(sizeof(TreeNode));\n    node->val = val;\n    node->height = 0;\n    node->left = NULL;\n    node->right = NULL;\n    return node;\n}\n
/* Binary tree node */\nclass TreeNode(val _val: Int) {  // Node value\n    val left: TreeNode? = null   // Reference to left child node\n    val right: TreeNode? = null  // Reference to right child node\n}\n
### Binary tree node class ###\nclass TreeNode\n  attr_accessor :val    # Node value\n  attr_accessor :left   # Reference to left child node\n  attr_accessor :right  # Reference to right child node\n\n  def initialize(val)\n    @val = val\n  end\nend\n

Each node has two references (pointers), pointing respectively to the left-child node and right-child node. This node is called the parent node of these two child nodes. When given a node of a binary tree, we call the tree formed by this node's left child and all nodes below it the left subtree of this node. Similarly, the right subtree can be defined.

In a binary tree, except leaf nodes, all other nodes contain child nodes and non-empty subtrees. As shown in Figure 7-1, if \"Node 2\" is regarded as a parent node, its left and right child nodes are \"Node 4\" and \"Node 5\" respectively. The left subtree is formed by \"Node 4\" and all nodes beneath it, while the right subtree is formed by \"Node 5\" and all nodes beneath it.

Figure 7-1   Parent Node, child Node, subtree

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#711-common-terminology-of-binary-trees","level":2,"title":"7.1.1   Common Terminology of Binary Trees","text":"

The commonly used terminology of binary trees is shown in Figure 7-2.

  • Root node: The node at the top level of a binary tree, which does not have a parent node.
  • Leaf node: A node that does not have any child nodes, with both of its pointers pointing to None.
  • Edge: A line segment that connects two nodes, representing a reference (pointer) between the nodes.
  • The level of a node: It increases from top to bottom, with the root node being at level 1.
  • The degree of a node: The number of child nodes that a node has. In a binary tree, the degree can be 0, 1, or 2.
  • The height of a binary tree: The number of edges from the root node to the farthest leaf node.
  • The depth of a node: The number of edges from the root node to the node.
  • The height of a node: The number of edges from the farthest leaf node to the node.

Figure 7-2   Common Terminology of Binary Trees

Tip

Please note that we usually define \"height\" and \"depth\" as \"the number of edges traversed\", but some questions or textbooks may define them as \"the number of nodes traversed\". In this case, both height and depth need to be incremented by 1.

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#712-basic-operations-of-binary-trees","level":2,"title":"7.1.2   Basic Operations of Binary Trees","text":"","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#1-initializing-a-binary-tree","level":3,"title":"1.   Initializing a Binary Tree","text":"

Similar to a linked list, the initialization of a binary tree involves first creating the nodes and then establishing the references (pointers) between them.

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree.py
# Initializing a binary tree\n# Initializing nodes\nn1 = TreeNode(val=1)\nn2 = TreeNode(val=2)\nn3 = TreeNode(val=3)\nn4 = TreeNode(val=4)\nn5 = TreeNode(val=5)\n# Linking references (pointers) between nodes\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
binary_tree.cpp
/* Initializing a binary tree */\n// Initializing nodes\nTreeNode* n1 = new TreeNode(1);\nTreeNode* n2 = new TreeNode(2);\nTreeNode* n3 = new TreeNode(3);\nTreeNode* n4 = new TreeNode(4);\nTreeNode* n5 = new TreeNode(5);\n// Linking references (pointers) between nodes\nn1->left = n2;\nn1->right = n3;\nn2->left = n4;\nn2->right = n5;\n
binary_tree.java
// Initializing nodes\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// Linking references (pointers) between nodes\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.cs
/* Initializing a binary tree */\n// Initializing nodes\nTreeNode n1 = new(1);\nTreeNode n2 = new(2);\nTreeNode n3 = new(3);\nTreeNode n4 = new(4);\nTreeNode n5 = new(5);\n// Linking references (pointers) between nodes\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.go
/* Initializing a binary tree */\n// Initializing nodes\nn1 := NewTreeNode(1)\nn2 := NewTreeNode(2)\nn3 := NewTreeNode(3)\nn4 := NewTreeNode(4)\nn5 := NewTreeNode(5)\n// Linking references (pointers) between nodes\nn1.Left = n2\nn1.Right = n3\nn2.Left = n4\nn2.Right = n5\n
binary_tree.swift
// Initializing nodes\nlet n1 = TreeNode(x: 1)\nlet n2 = TreeNode(x: 2)\nlet n3 = TreeNode(x: 3)\nlet n4 = TreeNode(x: 4)\nlet n5 = TreeNode(x: 5)\n// Linking references (pointers) between nodes\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
binary_tree.js
/* Initializing a binary tree */\n// Initializing nodes\nlet n1 = new TreeNode(1),\n    n2 = new TreeNode(2),\n    n3 = new TreeNode(3),\n    n4 = new TreeNode(4),\n    n5 = new TreeNode(5);\n// Linking references (pointers) between nodes\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.ts
/* Initializing a binary tree */\n// Initializing nodes\nlet n1 = new TreeNode(1),\n    n2 = new TreeNode(2),\n    n3 = new TreeNode(3),\n    n4 = new TreeNode(4),\n    n5 = new TreeNode(5);\n// Linking references (pointers) between nodes\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.dart
/* Initializing a binary tree */\n// Initializing nodes\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// Linking references (pointers) between nodes\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
binary_tree.rs
// Initializing nodes\nlet n1 = TreeNode::new(1);\nlet n2 = TreeNode::new(2);\nlet n3 = TreeNode::new(3);\nlet n4 = TreeNode::new(4);\nlet n5 = TreeNode::new(5);\n// Linking references (pointers) between nodes\nn1.borrow_mut().left = Some(n2.clone());\nn1.borrow_mut().right = Some(n3);\nn2.borrow_mut().left = Some(n4);\nn2.borrow_mut().right = Some(n5);\n
binary_tree.c
/* Initializing a binary tree */\n// Initializing nodes\nTreeNode *n1 = newTreeNode(1);\nTreeNode *n2 = newTreeNode(2);\nTreeNode *n3 = newTreeNode(3);\nTreeNode *n4 = newTreeNode(4);\nTreeNode *n5 = newTreeNode(5);\n// Linking references (pointers) between nodes\nn1->left = n2;\nn1->right = n3;\nn2->left = n4;\nn2->right = n5;\n
binary_tree.kt
// Initializing nodes\nval n1 = TreeNode(1)\nval n2 = TreeNode(2)\nval n3 = TreeNode(3)\nval n4 = TreeNode(4)\nval n5 = TreeNode(5)\n// Linking references (pointers) between nodes\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
binary_tree.rb
# Initializing a binary tree\n# Initializing nodes\nn1 = TreeNode.new(1)\nn2 = TreeNode.new(2)\nn3 = TreeNode.new(3)\nn4 = TreeNode.new(4)\nn5 = TreeNode.new(5)\n# Linking references (pointers) between nodes\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
Code Visualization

Full Screen >

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#2-inserting-and-removing-nodes","level":3,"title":"2.   Inserting and Removing Nodes","text":"

Similar to a linked list, inserting and removing nodes in a binary tree can be achieved by modifying pointers. Figure 7-3 provides an example.

Figure 7-3   Inserting and removing nodes in a binary tree

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree.py
# Inserting and removing nodes\np = TreeNode(0)\n# Inserting node P between n1 -> n2\nn1.left = p\np.left = n2\n# Removing node P\nn1.left = n2\n
binary_tree.cpp
/* Inserting and removing nodes */\nTreeNode* P = new TreeNode(0);\n// Inserting node P between n1 and n2\nn1->left = P;\nP->left = n2;\n// Removing node P\nn1->left = n2;\n
binary_tree.java
TreeNode P = new TreeNode(0);\n// Inserting node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Removing node P\nn1.left = n2;\n
binary_tree.cs
/* Inserting and removing nodes */\nTreeNode P = new(0);\n// Inserting node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Removing node P\nn1.left = n2;\n
binary_tree.go
/* Inserting and removing nodes */\n// Inserting node P between n1 and n2\np := NewTreeNode(0)\nn1.Left = p\np.Left = n2\n// Removing node P\nn1.Left = n2\n
binary_tree.swift
let P = TreeNode(x: 0)\n// Inserting node P between n1 and n2\nn1.left = P\nP.left = n2\n// Removing node P\nn1.left = n2\n
binary_tree.js
/* Inserting and removing nodes */\nlet P = new TreeNode(0);\n// Inserting node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Removing node P\nn1.left = n2;\n
binary_tree.ts
/* Inserting and removing nodes */\nconst P = new TreeNode(0);\n// Inserting node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Removing node P\nn1.left = n2;\n
binary_tree.dart
/* Inserting and removing nodes */\nTreeNode P = new TreeNode(0);\n// Inserting node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Removing node P\nn1.left = n2;\n
binary_tree.rs
let p = TreeNode::new(0);\n// Inserting node P between n1 and n2\nn1.borrow_mut().left = Some(p.clone());\np.borrow_mut().left = Some(n2.clone());\n// Removing node P\nn1.borrow_mut().left = Some(n2);\n
binary_tree.c
/* Inserting and removing nodes */\nTreeNode *P = newTreeNode(0);\n// Inserting node P between n1 and n2\nn1->left = P;\nP->left = n2;\n// Removing node P\nn1->left = n2;\n
binary_tree.kt
val P = TreeNode(0)\n// Inserting node P between n1 and n2\nn1.left = P\nP.left = n2\n// Removing node P\nn1.left = n2\n
binary_tree.rb
# Inserting and removing nodes\n_p = TreeNode.new(0)\n# Inserting node _p between n1 and n2\nn1.left = _p\n_p.left = n2\n# Removing node _p\nn1.left = n2\n
Code Visualization

Full Screen >

Tip

It should be noted that inserting nodes may change the original logical structure of the binary tree, while removing nodes typically involves removing the node and all its subtrees. Therefore, in a binary tree, insertion and removal are usually performed through a set of operations to achieve meaningful outcomes.

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#713-common-types-of-binary-trees","level":2,"title":"7.1.3   Common Types of Binary Trees","text":"","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#1-perfect-binary-tree","level":3,"title":"1.   Perfect Binary Tree","text":"

As shown in Figure 7-4, a perfect binary tree has all levels completely filled with nodes. In a perfect binary tree, leaf nodes have a degree of \\(0\\), while all other nodes have a degree of \\(2\\). If the tree height is \\(h\\), the total number of nodes is \\(2^{h+1} - 1\\), exhibiting a standard exponential relationship that reflects the common phenomenon of cell division in nature.

Tip

Please note that in the Chinese community, a perfect binary tree is often referred to as a full binary tree.

Figure 7-4   Perfect binary tree

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#2-complete-binary-tree","level":3,"title":"2.   Complete Binary Tree","text":"

As shown in Figure 7-5, a complete binary tree only allows the bottom level to be incompletely filled, and the nodes at the bottom level must be filled continuously from left to right. Note that a perfect binary tree is also a complete binary tree.

Figure 7-5   Complete binary tree

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#3-full-binary-tree","level":3,"title":"3.   Full Binary Tree","text":"

As shown in Figure 7-6, in a full binary tree, all nodes except leaf nodes have two child nodes.

Figure 7-6   Full binary tree

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#4-balanced-binary-tree","level":3,"title":"4.   Balanced Binary Tree","text":"

As shown in Figure 7-7, in a balanced binary tree, the absolute difference between the height of the left and right subtrees of any node does not exceed 1.

Figure 7-7   Balanced binary tree

","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree/#714-degeneration-of-binary-trees","level":2,"title":"7.1.4   Degeneration of Binary Trees","text":"

Figure 7-8 shows the ideal and degenerate structures of binary trees. When every level of a binary tree is filled, it reaches the \"perfect binary tree\" state; when all nodes are biased toward one side, the binary tree degenerates into a \"linked list\".

  • A perfect binary tree is the ideal case, fully leveraging the \"divide and conquer\" advantage of binary trees.
  • A linked list represents the other extreme, where all operations become linear operations with time complexity degrading to \\(O(n)\\).

Figure 7-8   The Best and Worst Structures of Binary Trees

As shown in Table 7-1, in the best and worst structures, the binary tree achieves either maximum or minimum values for leaf node count, total number of nodes, and height.

Table 7-1   The Best and Worst Structures of Binary Trees

Perfect binary tree Linked list Number of nodes at level \\(i\\) \\(2^{i-1}\\) \\(1\\) Number of leaf nodes in a tree with height \\(h\\) \\(2^h\\) \\(1\\) Total number of nodes in a tree with height \\(h\\) \\(2^{h+1} - 1\\) \\(h + 1\\) Height of a tree with \\(n\\) total nodes \\(\\log_2 (n+1) - 1\\) \\(n - 1\\)","path":["Chapter 7. Tree","7.1   Binary Tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/","level":1,"title":"7.2   Binary Tree Traversal","text":"

From a physical structure perspective, a tree is a data structure based on linked lists. Hence, its traversal method involves accessing nodes one by one through pointers. However, a tree is a non-linear data structure, which makes traversing a tree more complex than traversing a linked list, requiring the assistance of search algorithms.

The common traversal methods for binary trees include level-order traversal, pre-order traversal, in-order traversal, and post-order traversal.

","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#721-level-order-traversal","level":2,"title":"7.2.1   Level-Order Traversal","text":"

As shown in Figure 7-9, level-order traversal traverses the binary tree from top to bottom, layer by layer. Within each level, it visits nodes from left to right.

Level-order traversal is essentially breadth-first traversal, also known as breadth-first search (BFS), which embodies a \"expanding outward circle by circle\" layer-by-layer traversal method.

Figure 7-9   Level-order traversal of a binary tree

","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#1-code-implementation","level":3,"title":"1.   Code Implementation","text":"

Breadth-first traversal is typically implemented with the help of a \"queue\". The queue follows the \"first in, first out\" rule, while breadth-first traversal follows the \"layer-by-layer progression\" rule; the underlying ideas of the two are consistent. The implementation code is as follows:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree_bfs.py
def level_order(root: TreeNode | None) -> list[int]:\n    \"\"\"Level-order traversal\"\"\"\n    # Initialize queue, add root node\n    queue: deque[TreeNode] = deque()\n    queue.append(root)\n    # Initialize a list to save the traversal sequence\n    res = []\n    while queue:\n        node: TreeNode = queue.popleft()  # Dequeue\n        res.append(node.val)  # Save node value\n        if node.left is not None:\n            queue.append(node.left)  # Left child node enqueue\n        if node.right is not None:\n            queue.append(node.right)  # Right child node enqueue\n    return res\n
binary_tree_bfs.cpp
/* Level-order traversal */\nvector<int> levelOrder(TreeNode *root) {\n    // Initialize queue, add root node\n    queue<TreeNode *> queue;\n    queue.push(root);\n    // Initialize a list to save the traversal sequence\n    vector<int> vec;\n    while (!queue.empty()) {\n        TreeNode *node = queue.front();\n        queue.pop();              // Dequeue\n        vec.push_back(node->val); // Save node value\n        if (node->left != nullptr)\n            queue.push(node->left); // Left child node enqueue\n        if (node->right != nullptr)\n            queue.push(node->right); // Right child node enqueue\n    }\n    return vec;\n}\n
binary_tree_bfs.java
/* Level-order traversal */\nList<Integer> levelOrder(TreeNode root) {\n    // Initialize queue, add root node\n    Queue<TreeNode> queue = new LinkedList<>();\n    queue.add(root);\n    // Initialize a list to save the traversal sequence\n    List<Integer> list = new ArrayList<>();\n    while (!queue.isEmpty()) {\n        TreeNode node = queue.poll(); // Dequeue\n        list.add(node.val);           // Save node value\n        if (node.left != null)\n            queue.offer(node.left);   // Left child node enqueue\n        if (node.right != null)\n            queue.offer(node.right);  // Right child node enqueue\n    }\n    return list;\n}\n
binary_tree_bfs.cs
/* Level-order traversal */\nList<int> LevelOrder(TreeNode root) {\n    // Initialize queue, add root node\n    Queue<TreeNode> queue = new();\n    queue.Enqueue(root);\n    // Initialize a list to save the traversal sequence\n    List<int> list = [];\n    while (queue.Count != 0) {\n        TreeNode node = queue.Dequeue(); // Dequeue\n        list.Add(node.val!.Value);       // Save node value\n        if (node.left != null)\n            queue.Enqueue(node.left);    // Left child node enqueue\n        if (node.right != null)\n            queue.Enqueue(node.right);   // Right child node enqueue\n    }\n    return list;\n}\n
binary_tree_bfs.go
/* Level-order traversal */\nfunc levelOrder(root *TreeNode) []any {\n    // Initialize queue, add root node\n    queue := list.New()\n    queue.PushBack(root)\n    // Initialize a slice to save traversal sequence\n    nums := make([]any, 0)\n    for queue.Len() > 0 {\n        // Dequeue\n        node := queue.Remove(queue.Front()).(*TreeNode)\n        // Save node value\n        nums = append(nums, node.Val)\n        if node.Left != nil {\n            // Left child node enqueue\n            queue.PushBack(node.Left)\n        }\n        if node.Right != nil {\n            // Right child node enqueue\n            queue.PushBack(node.Right)\n        }\n    }\n    return nums\n}\n
binary_tree_bfs.swift
/* Level-order traversal */\nfunc levelOrder(root: TreeNode) -> [Int] {\n    // Initialize queue, add root node\n    var queue: [TreeNode] = [root]\n    // Initialize a list to save the traversal sequence\n    var list: [Int] = []\n    while !queue.isEmpty {\n        let node = queue.removeFirst() // Dequeue\n        list.append(node.val) // Save node value\n        if let left = node.left {\n            queue.append(left) // Left child node enqueue\n        }\n        if let right = node.right {\n            queue.append(right) // Right child node enqueue\n        }\n    }\n    return list\n}\n
binary_tree_bfs.js
/* Level-order traversal */\nfunction levelOrder(root) {\n    // Initialize queue, add root node\n    const queue = [root];\n    // Initialize a list to save the traversal sequence\n    const list = [];\n    while (queue.length) {\n        let node = queue.shift(); // Dequeue\n        list.push(node.val); // Save node value\n        if (node.left) queue.push(node.left); // Left child node enqueue\n        if (node.right) queue.push(node.right); // Right child node enqueue\n    }\n    return list;\n}\n
binary_tree_bfs.ts
/* Level-order traversal */\nfunction levelOrder(root: TreeNode | null): number[] {\n    // Initialize queue, add root node\n    const queue = [root];\n    // Initialize a list to save the traversal sequence\n    const list: number[] = [];\n    while (queue.length) {\n        let node = queue.shift() as TreeNode; // Dequeue\n        list.push(node.val); // Save node value\n        if (node.left) {\n            queue.push(node.left); // Left child node enqueue\n        }\n        if (node.right) {\n            queue.push(node.right); // Right child node enqueue\n        }\n    }\n    return list;\n}\n
binary_tree_bfs.dart
/* Level-order traversal */\nList<int> levelOrder(TreeNode? root) {\n  // Initialize queue, add root node\n  Queue<TreeNode?> queue = Queue();\n  queue.add(root);\n  // Initialize a list to save the traversal sequence\n  List<int> res = [];\n  while (queue.isNotEmpty) {\n    TreeNode? node = queue.removeFirst(); // Dequeue\n    res.add(node!.val); // Save node value\n    if (node.left != null) queue.add(node.left); // Left child node enqueue\n    if (node.right != null) queue.add(node.right); // Right child node enqueue\n  }\n  return res;\n}\n
binary_tree_bfs.rs
/* Level-order traversal */\nfn level_order(root: &Rc<RefCell<TreeNode>>) -> Vec<i32> {\n    // Initialize queue, add root node\n    let mut que = VecDeque::new();\n    que.push_back(root.clone());\n    // Initialize a list to save the traversal sequence\n    let mut vec = Vec::new();\n\n    while let Some(node) = que.pop_front() {\n        // Dequeue\n        vec.push(node.borrow().val); // Save node value\n        if let Some(left) = node.borrow().left.as_ref() {\n            que.push_back(left.clone()); // Left child node enqueue\n        }\n        if let Some(right) = node.borrow().right.as_ref() {\n            que.push_back(right.clone()); // Right child node enqueue\n        };\n    }\n    vec\n}\n
binary_tree_bfs.c
/* Level-order traversal */\nint *levelOrder(TreeNode *root, int *size) {\n    /* Auxiliary queue */\n    int front, rear;\n    int index, *arr;\n    TreeNode *node;\n    TreeNode **queue;\n\n    /* Auxiliary queue */\n    queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE);\n    // Queue pointer\n    front = 0, rear = 0;\n    // Add root node\n    queue[rear++] = root;\n    // Initialize a list to save the traversal sequence\n    /* Auxiliary array */\n    arr = (int *)malloc(sizeof(int) * MAX_SIZE);\n    // Array pointer\n    index = 0;\n    while (front < rear) {\n        // Dequeue\n        node = queue[front++];\n        // Save node value\n        arr[index++] = node->val;\n        if (node->left != NULL) {\n            // Left child node enqueue\n            queue[rear++] = node->left;\n        }\n        if (node->right != NULL) {\n            // Right child node enqueue\n            queue[rear++] = node->right;\n        }\n    }\n    // Update array length value\n    *size = index;\n    arr = realloc(arr, sizeof(int) * (*size));\n\n    // Free auxiliary array space\n    free(queue);\n    return arr;\n}\n
binary_tree_bfs.kt
/* Level-order traversal */\nfun levelOrder(root: TreeNode?): MutableList<Int> {\n    // Initialize queue, add root node\n    val queue = LinkedList<TreeNode?>()\n    queue.add(root)\n    // Initialize a list to save the traversal sequence\n    val list = mutableListOf<Int>()\n    while (queue.isNotEmpty()) {\n        val node = queue.poll()      // Dequeue\n        list.add(node?._val!!)       // Save node value\n        if (node.left != null)\n            queue.offer(node.left)   // Left child node enqueue\n        if (node.right != null)\n            queue.offer(node.right)  // Right child node enqueue\n    }\n    return list\n}\n
binary_tree_bfs.rb
### Level-order traversal ###\ndef level_order(root)\n  # Initialize queue, add root node\n  queue = [root]\n  # Initialize a list to save the traversal sequence\n  res = []\n  while !queue.empty?\n    node = queue.shift # Dequeue\n    res << node.val # Save node value\n    queue << node.left unless node.left.nil? # Left child node enqueue\n    queue << node.right unless node.right.nil? # Right child node enqueue\n  end\n  res\nend\n
","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#2-complexity-analysis","level":3,"title":"2.   Complexity Analysis","text":"
  • Time complexity is \\(O(n)\\): All nodes are visited once, using \\(O(n)\\) time, where \\(n\\) is the number of nodes.
  • Space complexity is \\(O(n)\\): In the worst case, i.e., a full binary tree, before traversing to the bottom level, the queue contains at most \\((n + 1) / 2\\) nodes simultaneously, occupying \\(O(n)\\) space.
","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#722-preorder-inorder-and-postorder-traversal","level":2,"title":"7.2.2   Preorder, Inorder, and Postorder Traversal","text":"

Correspondingly, preorder, inorder, and postorder traversals all belong to depth-first traversal, also known as depth-first search (DFS), which embodies a \"first go to the end, then backtrack and continue\" traversal method.

Figure 7-10 shows how depth-first traversal works on a binary tree. Depth-first traversal is like \"walking\" around the perimeter of the entire binary tree, encountering three positions at each node, corresponding to preorder, inorder, and postorder traversal.

Figure 7-10   Preorder, inorder, and postorder traversal of a binary tree

","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#1-code-implementation_1","level":3,"title":"1.   Code Implementation","text":"

Depth-first search is usually implemented based on recursion:

PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree_dfs.py
def pre_order(root: TreeNode | None):\n    \"\"\"Preorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit priority: root node -> left subtree -> right subtree\n    res.append(root.val)\n    pre_order(root=root.left)\n    pre_order(root=root.right)\n\ndef in_order(root: TreeNode | None):\n    \"\"\"Inorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit priority: left subtree -> root node -> right subtree\n    in_order(root=root.left)\n    res.append(root.val)\n    in_order(root=root.right)\n\ndef post_order(root: TreeNode | None):\n    \"\"\"Postorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit priority: left subtree -> right subtree -> root node\n    post_order(root=root.left)\n    post_order(root=root.right)\n    res.append(root.val)\n
binary_tree_dfs.cpp
/* Preorder traversal */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit priority: root node -> left subtree -> right subtree\n    vec.push_back(root->val);\n    preOrder(root->left);\n    preOrder(root->right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root->left);\n    vec.push_back(root->val);\n    inOrder(root->right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root->left);\n    postOrder(root->right);\n    vec.push_back(root->val);\n}\n
binary_tree_dfs.java
/* Preorder traversal */\nvoid preOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit priority: root node -> left subtree -> right subtree\n    list.add(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root.left);\n    list.add(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root.left);\n    postOrder(root.right);\n    list.add(root.val);\n}\n
binary_tree_dfs.cs
/* Preorder traversal */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit priority: root node -> left subtree -> right subtree\n    list.Add(root.val!.Value);\n    PreOrder(root.left);\n    PreOrder(root.right);\n}\n\n/* Inorder traversal */\nvoid InOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit priority: left subtree -> root node -> right subtree\n    InOrder(root.left);\n    list.Add(root.val!.Value);\n    InOrder(root.right);\n}\n\n/* Postorder traversal */\nvoid PostOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit priority: left subtree -> right subtree -> root node\n    PostOrder(root.left);\n    PostOrder(root.right);\n    list.Add(root.val!.Value);\n}\n
binary_tree_dfs.go
/* Preorder traversal */\nfunc preOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit priority: root node -> left subtree -> right subtree\n    nums = append(nums, node.Val)\n    preOrder(node.Left)\n    preOrder(node.Right)\n}\n\n/* Inorder traversal */\nfunc inOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(node.Left)\n    nums = append(nums, node.Val)\n    inOrder(node.Right)\n}\n\n/* Postorder traversal */\nfunc postOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(node.Left)\n    postOrder(node.Right)\n    nums = append(nums, node.Val)\n}\n
binary_tree_dfs.swift
/* Preorder traversal */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit priority: root node -> left subtree -> right subtree\n    list.append(root.val)\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n}\n\n/* Inorder traversal */\nfunc inOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root: root.left)\n    list.append(root.val)\n    inOrder(root: root.right)\n}\n\n/* Postorder traversal */\nfunc postOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root: root.left)\n    postOrder(root: root.right)\n    list.append(root.val)\n}\n
binary_tree_dfs.js
/* Preorder traversal */\nfunction preOrder(root) {\n    if (root === null) return;\n    // Visit priority: root node -> left subtree -> right subtree\n    list.push(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nfunction inOrder(root) {\n    if (root === null) return;\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root.left);\n    list.push(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nfunction postOrder(root) {\n    if (root === null) return;\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root.left);\n    postOrder(root.right);\n    list.push(root.val);\n}\n
binary_tree_dfs.ts
/* Preorder traversal */\nfunction preOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit priority: root node -> left subtree -> right subtree\n    list.push(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nfunction inOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root.left);\n    list.push(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nfunction postOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root.left);\n    postOrder(root.right);\n    list.push(root.val);\n}\n
binary_tree_dfs.dart
/* Preorder traversal */\nvoid preOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit priority: root node -> left subtree -> right subtree\n  list.add(node.val);\n  preOrder(node.left);\n  preOrder(node.right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit priority: left subtree -> root node -> right subtree\n  inOrder(node.left);\n  list.add(node.val);\n  inOrder(node.right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit priority: left subtree -> right subtree -> root node\n  postOrder(node.left);\n  postOrder(node.right);\n  list.add(node.val);\n}\n
binary_tree_dfs.rs
/* Preorder traversal */\nfn pre_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit priority: root node -> left subtree -> right subtree\n            let node = node.borrow();\n            res.push(node.val);\n            dfs(node.left.as_ref(), res);\n            dfs(node.right.as_ref(), res);\n        }\n    }\n    dfs(root, &mut result);\n\n    result\n}\n\n/* Inorder traversal */\nfn in_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit priority: left subtree -> root node -> right subtree\n            let node = node.borrow();\n            dfs(node.left.as_ref(), res);\n            res.push(node.val);\n            dfs(node.right.as_ref(), res);\n        }\n    }\n    dfs(root, &mut result);\n\n    result\n}\n\n/* Postorder traversal */\nfn post_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit priority: left subtree -> right subtree -> root node\n            let node = node.borrow();\n            dfs(node.left.as_ref(), res);\n            dfs(node.right.as_ref(), res);\n            res.push(node.val);\n        }\n    }\n\n    dfs(root, &mut result);\n\n    result\n}\n
binary_tree_dfs.c
/* Preorder traversal */\nvoid preOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit priority: root node -> left subtree -> right subtree\n    arr[(*size)++] = root->val;\n    preOrder(root->left, size);\n    preOrder(root->right, size);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root->left, size);\n    arr[(*size)++] = root->val;\n    inOrder(root->right, size);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root->left, size);\n    postOrder(root->right, size);\n    arr[(*size)++] = root->val;\n}\n
binary_tree_dfs.kt
/* Preorder traversal */\nfun preOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit priority: root node -> left subtree -> right subtree\n    list.add(root._val)\n    preOrder(root.left)\n    preOrder(root.right)\n}\n\n/* Inorder traversal */\nfun inOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit priority: left subtree -> root node -> right subtree\n    inOrder(root.left)\n    list.add(root._val)\n    inOrder(root.right)\n}\n\n/* Postorder traversal */\nfun postOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit priority: left subtree -> right subtree -> root node\n    postOrder(root.left)\n    postOrder(root.right)\n    list.add(root._val)\n}\n
binary_tree_dfs.rb
### Pre-order traversal ###\ndef pre_order(root)\n  return if root.nil?\n\n  # Visit priority: root node -> left subtree -> right subtree\n  $res << root.val\n  pre_order(root.left)\n  pre_order(root.right)\nend\n\n### In-order traversal ###\ndef in_order(root)\n  return if root.nil?\n\n  # Visit priority: left subtree -> root node -> right subtree\n  in_order(root.left)\n  $res << root.val\n  in_order(root.right)\nend\n\n### Post-order traversal ###\ndef post_order(root)\n  return if root.nil?\n\n  # Visit priority: left subtree -> right subtree -> root node\n  post_order(root.left)\n  post_order(root.right)\n  $res << root.val\nend\n

Tip

Depth-first search can also be implemented based on iteration, interested readers can study this on their own.

Figure 7-11 shows the recursive process of preorder traversal of a binary tree, which can be divided into two opposite parts: \"recursion\" and \"return\".

  1. \"Recursion\" means opening a new method, where the program accesses the next node in this process.
  2. \"Return\" means the function returns, indicating that the current node has been fully visited.
<1><2><3><4><5><6><7><8><9><10><11>

Figure 7-11   The recursive process of preorder traversal

","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#2-complexity-analysis_1","level":3,"title":"2.   Complexity Analysis","text":"
  • Time complexity is \\(O(n)\\): All nodes are visited once, using \\(O(n)\\) time.
  • Space complexity is \\(O(n)\\): In the worst case, i.e., the tree degenerates into a linked list, the recursion depth reaches \\(n\\), and the system occupies \\(O(n)\\) stack frame space.
","path":["Chapter 7. Tree","7.2   Binary Tree Traversal"],"tags":[]},{"location":"chapter_tree/summary/","level":1,"title":"7.6   Summary","text":"","path":["Chapter 7. Tree","7.6   Summary"],"tags":[]},{"location":"chapter_tree/summary/#1-key-review","level":3,"title":"1.   Key Review","text":"
  • A binary tree is a non-linear data structure that embodies the divide-and-conquer logic of \"one divides into two\". Each binary tree node contains a value and two pointers, which respectively point to its left and right child nodes.
  • For a certain node in a binary tree, the tree formed by its left (right) child node and all nodes below is called the left (right) subtree of that node.
  • Related terminology of binary trees includes root node, leaf node, level, degree, edge, height, and depth.
  • The initialization, node insertion, and node removal operations of binary trees are similar to those of linked lists.
  • Common types of binary trees include perfect binary trees, complete binary trees, full binary trees, and balanced binary trees. The perfect binary tree is the ideal state, while the linked list is the worst state after degradation.
  • A binary tree can be represented using an array by arranging node values and empty slots in level-order traversal sequence, and implementing pointers based on the index mapping relationship between parent and child nodes.
  • Level-order traversal of a binary tree is a breadth-first search method, embodying a layer-by-layer traversal approach of \"expanding outward circle by circle\", typically implemented using a queue.
  • Preorder, inorder, and postorder traversals all belong to depth-first search, embodying a traversal approach of \"first go to the end, then backtrack and continue\", typically implemented using recursion.
  • A binary search tree is an efficient data structure for element searching, with search, insertion, and removal operations all having time complexity of \\(O(\\log n)\\). When a binary search tree degenerates into a linked list, all time complexities degrade to \\(O(n)\\).
  • An AVL tree, also known as a balanced binary search tree, ensures the tree remains balanced after continuous node insertions and removals through rotation operations.
  • Rotation operations in AVL trees include right rotation, left rotation, left rotation then right rotation, and right rotation then left rotation. After inserting or removing nodes, AVL trees perform rotation operations from bottom to top to restore the tree to balance.
","path":["Chapter 7. Tree","7.6   Summary"],"tags":[]},{"location":"chapter_tree/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

Q: For a binary tree with only one node, are both the height of the tree and the depth of the root node \\(0\\)?

Yes, because height and depth are typically defined as \"the number of edges passed.\"

Q: The insertion and removal in a binary tree are generally accomplished by a set of operations. What does \"a set of operations\" refer to here? Does it imply releasing the resources of the child nodes?

Taking the binary search tree as an example, the operation of removing a node needs to be handled in three different scenarios, each requiring multiple steps of node operations.

Q: Why does DFS traversal of binary trees have three orders: preorder, inorder, and postorder, and what are their uses?

Similar to forward and reverse traversal of arrays, preorder, inorder, and postorder traversals are three methods of binary tree traversal that allow us to obtain a traversal result in a specific order. For example, in a binary search tree, since nodes satisfy the relationship left child node value < root node value < right child node value, we only need to traverse the tree with the priority of \"left \\(\\rightarrow\\) root \\(\\rightarrow\\) right\" to obtain an ordered node sequence.

Q: In a right rotation operation handling the relationship between unbalanced nodes node, child, and grand_child, doesn't the connection between node and its parent node get lost after the right rotation?

We need to view this problem from a recursive perspective. The right rotation operation right_rotate(root) passes in the root node of the subtree and eventually returns the root node of the subtree after rotation with return child. The connection between the subtree's root node and its parent node is completed after the function returns, which is not within the maintenance scope of the right rotation operation.

Q: In C++, functions are divided into private and public sections. What considerations are there for this? Why are the height() function and the updateHeight() function placed in public and private, respectively?

It mainly depends on the method's usage scope. If a method is only used within the class, then it is designed as private. For example, calling updateHeight() alone by the user makes no sense, as it is only a step in insertion or removal operations. However, height() is used to access node height, similar to vector.size(), so it is set to public for ease of use.

Q: How do you build a binary search tree from a set of input data? Is the choice of root node very important?

Yes, the method for building a tree is provided in the build_tree() method in the binary search tree code. As for the choice of root node, we typically sort the input data, then select the middle element as the root node, and recursively build the left and right subtrees. This approach maximizes the tree's balance.

Q: In Java, do you always have to use the equals() method for string comparison?

In Java, for primitive data types, == is used to compare whether the values of two variables are equal. For reference types, the working principles of the two symbols are different.

  • ==: Used to compare whether two variables point to the same object, i.e., whether their positions in memory are the same.
  • equals(): Used to compare whether the values of two objects are equal.

Therefore, if we want to compare values, we should use equals(). However, strings initialized via String a = \"hi\"; String b = \"hi\"; are stored in the string constant pool and point to the same object, so a == b can also be used to compare the contents of the two strings.

Q: Before reaching the bottom level, is the number of nodes in the queue \\(2^h\\) in breadth-first traversal?

Yes, for example, a full binary tree with height \\(h = 2\\) has a total of \\(n = 7\\) nodes, then the bottom level has \\(4 = 2^h = (n + 1) / 2\\) nodes.

","path":["Chapter 7. Tree","7.6   Summary"],"tags":[]}]} \ No newline at end of file diff --git a/en/stylesheets/extra.css b/en/stylesheets/extra.css index 57becd0d3..00a71c3b1 100644 --- a/en/stylesheets/extra.css +++ b/en/stylesheets/extra.css @@ -1,4 +1,4 @@ -/* Color Settings */ +/* Theme tokens */ /* https://github.com/squidfunk/mkdocs-material/blob/6b5035f5580f97532d664e3d1babf5f320e88ee9/src/assets/stylesheets/main/_colors.scss */ /* https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#custom-colors */ :root>* { @@ -14,21 +14,23 @@ --md-code-fg-color: #1d1d20; --md-code-bg-color: #f5f5f5; - --md-accent-fg-color: #999; - --md-typeset-color: #1d1d20; --md-typeset-a-color: #349890; + --md-accent-fg-color: var(--md-typeset-a-color); + --md-typeset-btn-color: #55aea6; --md-typeset-btn-hover-color: #52bbb1; --md-admonition-icon--pythontutor: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-pythontutor-color: #eee; + --md-admonition-pythontutor-color: var(--md-code-bg-color); + + --hello-algo-sidebar-width: 13rem; } [data-md-color-scheme="slate"] { - --theme-dark-base: #1E1E1E; - --theme-dark-mantle: #1A1A1A; + --theme-dark-base: #1e1e1e; + --theme-dark-mantle: #1a1a1a; --theme-dark-crust: #171717; --hero-starfield-bg-color: var(--theme-dark-base); @@ -37,25 +39,25 @@ --md-default-fg-color: #adbac7; --md-default-bg-color: var(--theme-dark-base); + --md-default-bg-color--light: rgb(30 30 30 / 0.8); - --md-body-bg-color: var(--theme-dark-mantle); + --md-body-bg-color: var(--md-default-bg-color); --md-header-bg-color: rgba(26, 26, 26, 0.8); --md-code-fg-color: #adbac7; --md-code-bg-color: var(--theme-dark-crust); - --md-accent-fg-color: #aaa; - - --md-footer-fg-color: #adbac7; - --md-footer-bg-color: var(--theme-dark-mantle); - --md-typeset-color: #adbac7; --md-typeset-a-color: #52bbb1; + --md-accent-fg-color: var(--md-typeset-a-color); --md-typeset-btn-color: #52bbb1; --md-typeset-btn-hover-color: #55aea6; - --md-admonition-pythontutor-color: var(--theme-dark-crust); + --md-footer-fg-color: #adbac7; + --md-footer-bg-color: var(--theme-dark-mantle); + + --md-admonition-pythontutor-color: var(--md-code-bg-color); } [data-md-color-scheme="slate"][data-md-color-primary="black"], @@ -63,24 +65,82 @@ --md-typeset-a-color: #52bbb1; } +/* Base layout */ +body { + background-color: var(--md-default-bg-color); + --md-text-font-family: -apple-system, BlinkMacSystemFont, + var(--md-text-font, _), Helvetica, Arial, sans-serif; + --md-code-font-family: var(--md-code-font, _), SFMono-Regular, Consolas, Menlo, + -apple-system, BlinkMacSystemFont, var(--md-text-font, _), monospace; +} + +html:has(body[data-md-color-scheme="slate"]) { + background-color: #1e1e1e; +} + +html:has(body[data-md-color-scheme="default"]) { + background-color: #ffffff; +} + +@media screen and (min-width: 76.25em) { + .md-grid { + max-width: calc(61rem + 2 * (var(--hello-algo-sidebar-width) - 12.1rem)); + } + + .md-sidebar--primary, + .md-sidebar--secondary { + width: var(--hello-algo-sidebar-width); + } + + [dir="ltr"] .md-sidebar__inner { + padding-right: calc(100% - (var(--hello-algo-sidebar-width) - 0.6rem)); + } + + [dir="rtl"] .md-sidebar__inner { + padding-left: calc(100% - (var(--hello-algo-sidebar-width) - 0.6rem)); + } +} + +.md-sidebar--primary .md-sidebar__scrollwrap { + scrollbar-color: var(--md-default-fg-color--lighter) transparent; +} + +.md-sidebar--primary .md-sidebar__scrollwrap::-webkit-scrollbar-thumb, +.md-sidebar--primary .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover { + background-color: var(--md-default-fg-color--lighter); +} + +/* Banner and footer */ +.md-banner { + background-color: var(--md-default-bg-color); + color: var(--md-default-fg-color); + font-size: 0.75rem; +} + +.md-banner .banner-svg svg { + margin-right: 0.3rem; + height: 0.63rem; + fill: var(--md-default-fg-color); +} + +.md-footer, +.md-footer__inner, +.md-footer-meta, +.md-footer__link, +.md-footer__link:hover { + background-color: var(--md-default-bg-color); +} + +.md-footer { + border-top: 0.05rem solid var(--md-default-fg-color--lightest); +} + [data-md-color-scheme="slate"] .md-footer, -[data-md-color-scheme="slate"] .md-footer__inner { - background-color: var(--theme-dark-mantle); - color: var(--md-footer-fg-color); -} - -[data-md-color-scheme="slate"] .md-footer-meta { - background-color: var(--theme-dark-crust); - color: var(--md-footer-fg-color); -} - -[data-md-color-scheme="slate"] .md-footer__link { - background-color: var(--theme-dark-crust); - color: var(--md-footer-fg-color); -} - +[data-md-color-scheme="slate"] .md-footer__inner, +[data-md-color-scheme="slate"] .md-footer-meta, +[data-md-color-scheme="slate"] .md-footer__link, [data-md-color-scheme="slate"] .md-footer__link:hover { - background-color: var(--theme-dark-base); + color: var(--md-footer-fg-color); } [data-md-color-scheme="slate"] .md-footer__title, @@ -93,40 +153,31 @@ color: var(--md-footer-fg-color); } -/* https://github.com/squidfunk/mkdocs-material/issues/4832#issuecomment-1374891676 */ -.md-nav__link[for] { - color: var(--md-default-fg-color) !important; -} - -/* Figure class */ +/* Shared content elements */ .animation-figure { border-radius: 0.3rem; display: block; margin: 0 auto; - box-shadow: var(--md-shadow-z2); + box-shadow: 0 0.03rem 0.16rem rgb(0 0 0 / 0.07); } -/* Cover image class */ .cover-image { width: 28rem; height: auto; border-radius: 0.3rem; display: block; margin: 0 auto; - box-shadow: var(--md-shadow-z2); + box-shadow: 0 0.03rem 0.16rem rgb(0 0 0 / 0.07); } -/* Center Markdown Tables (requires md_in_html extension) */ .center-table { text-align: center; } -/* Reset alignment for table cells */ .md-typeset .center-table :is(td, th):not([align]) { text-align: initial; } -/* Font size */ .md-typeset { font-size: 0.75rem; line-height: 1.5; @@ -136,7 +187,6 @@ font-size: 0.95em; } -/* Markdown Header */ /* https://github.com/squidfunk/mkdocs-material/blob/dcab57dd1cced4b77875c1aa1b53467c62709d31/src/assets/stylesheets/main/_typeset.scss */ .md-typeset h1 { font-weight: 400; @@ -155,11 +205,6 @@ text-transform: none; } -.md-typeset a:hover { - color: var(--md-typeset-a-color); - text-decoration: underline; -} - .md-typeset code { border-radius: 0.2rem; } @@ -168,21 +213,11 @@ font-weight: normal; } -/* font-family setting for Win10 */ -body { - --md-text-font-family: -apple-system, BlinkMacSystemFont, - var(--md-text-font, _), Helvetica, Arial, sans-serif; - --md-code-font-family: var(--md-code-font, _), SFMono-Regular, Consolas, Menlo, - -apple-system, BlinkMacSystemFont, var(--md-text-font, _), monospace; -} - -/* max height of code block */ /* https://github.com/squidfunk/mkdocs-material/issues/3444 */ .md-typeset pre>code { max-height: 25rem; } -/* Keep code block scrollbar hover neutral instead of accent-colored */ .md-typeset pre>code:hover { scrollbar-color: var(--md-default-fg-color--lighter) transparent; } @@ -191,29 +226,48 @@ body { background-color: var(--md-default-fg-color--lighter); } -/* Make the picture not glare in dark theme */ [data-md-color-scheme="slate"] .md-typeset img, [data-md-color-scheme="slate"] .md-typeset svg, [data-md-color-scheme="slate"] .md-typeset video { filter: brightness(0.85) invert(0.05); } -/* landing page */ -.header-img-div { - display: flex; - align-items: center; - justify-content: center; - margin: 0 auto; - width: 100%; - /* Default to full width */ +.md-typeset a:not(.md-button) { + text-decoration: none; +} + +.md-typeset a:not(.md-button):hover, +.md-typeset a:not(.md-button):focus-visible { + color: var(--md-typeset-a-color); + text-decoration: underline; +} + +/* Admonitions and tabs */ +.md-typeset .admonition-title:before, +.md-typeset summary:before, +.md-typeset summary:after { + top: 50%; +} + +.md-typeset .admonition-title:before, +.md-typeset summary:before { + transform: translateY(-50%); +} + +.md-typeset summary:after { + transform: translateY(-50%) rotate(0deg); +} + +.md-typeset details[open]>summary:after { + transform: translateY(-50%) rotate(90deg); } -/* Admonition for python tutor */ .md-typeset .admonition.pythontutor, .md-typeset details.pythontutor { border-color: var(--md-default-fg-color--lightest); margin-top: 0; margin-bottom: 1.5625em; + background-color: var(--md-code-bg-color); } .md-typeset .pythontutor>.admonition-title, @@ -228,26 +282,18 @@ body { mask-image: var(--md-admonition-icon--pythontutor); } -/* code block tabs */ +[data-md-color-scheme="slate"] .md-typeset details.pythontutor[open]> :not(summary), +[data-md-color-scheme="slate"] .md-typeset details.pythontutor[open]> :not(summary) :is(p, li, strong, em, sub, sup, code, a) { + background-color: #f5f5f5; + color: #1d1d20; +} + .md-typeset .tabbed-labels>label { font-size: 0.61rem; } .md-typeset .tabbed-labels--linked>label>a { - padding: .78125em 1.0em .625em; -} - -/* header banner */ -.md-banner { - background-color: var(--md-code-bg-color); - color: var(--md-default-fg-color); - font-size: 0.75rem; -} - -.md-banner .banner-svg svg { - margin-right: 0.3rem; - height: 0.63rem; - fill: var(--md-default-fg-color); + padding: 0.78125em 1em 0.625em; } .pythontutor-iframe { @@ -260,115 +306,55 @@ body { border: none; } -/* landing page container */ +/* Landing page layout */ +.header-img-div { + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto; + width: 100%; +} + .home-div { width: 100%; height: auto; display: flex; justify-content: center; align-items: center; + padding: 3em 2em; background-color: var(--md-default-bg-color); color: var(--md-default-fg-color); font-size: 0.9rem; - padding: 3em 2em; text-align: center; } +.home-div[data-md-color-scheme="default"], +.home-div[data-md-color-scheme="default"] h1, +.home-div[data-md-color-scheme="default"] h2, +.home-div[data-md-color-scheme="default"] h3, +.home-div[data-md-color-scheme="default"] h4, +.home-div[data-md-color-scheme="default"] h5, +.home-div[data-md-color-scheme="default"] h6, +.home-div[data-md-color-scheme="slate"], +.home-div[data-md-color-scheme="slate"] h1, +.home-div[data-md-color-scheme="slate"] h2, +.home-div[data-md-color-scheme="slate"] h3, +.home-div[data-md-color-scheme="slate"] h4, +.home-div[data-md-color-scheme="slate"] h5, +.home-div[data-md-color-scheme="slate"] h6 { + color: var(--md-default-fg-color); +} + .section-content { width: 100%; height: auto; max-width: 70vw; } -/* rounded button */ -.rounded-button { - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 10em; - margin: 0 0.1em; - padding: 0.6em 1.3em; - border: none; - background-color: var(--md-typeset-btn-color); - color: var(--md-primary-fg-color) !important; - text-align: center; - text-decoration: none; - cursor: pointer; -} - -.rounded-button:hover { - background-color: var(--md-typeset-btn-hover-color); -} - -.rounded-button span { - margin: 0; - margin-bottom: 0.07em; - white-space: nowrap; -} - -.rounded-button svg { - fill: var(--md-primary-fg-color); - width: auto; - height: 1.2em; - margin-right: 0.5em; -} - -/* device image */ -.device-on-hover { - width: auto; - transition: transform 0.3s ease-in-out, filter 0.3s ease-in-out; -} - -a:hover .device-on-hover { - filter: drop-shadow(0 0 0.2rem rgba(0, 0, 0, 0.15)); - transform: scale(1.01); -} - -/* text button */ -.reading-media { - display: flex; - justify-content: center; - align-items: flex-end; - height: 32vw; -} - -.media-block { - height: 100%; - margin: 0 0.2em; -} - -.text-button { - width: auto; - color: var(--md-typeset-btn-color); - text-decoration: none; - text-align: center; - margin: 2.7em auto; -} - -.text-button span { - white-space: nowrap; -} - -.text-button svg { - display: inline-block; - fill: var(--md-typeset-btn-color); - width: auto; - height: 0.9em; - background-size: cover; - padding-top: 0.17em; - margin-left: 0.15em; -} - -a:hover .text-button span { - text-decoration: underline; -} - -/* hero image */ .hero-div { height: min(84vh, 75vw); width: min(112vh, 100vw); - margin: 0 auto; - margin-top: -2.4rem; + margin: -2.4rem auto 0; padding: 0; position: relative; font-size: min(1.8vh, 2.5vw); @@ -384,13 +370,12 @@ a:hover .text-button span { } .hero-bg { - height: 100%; width: 100%; + height: 100%; object-fit: cover; position: absolute; } -/* hover on the planets */ .hero-div>a>img { width: auto; position: absolute; @@ -402,7 +387,6 @@ a:hover .text-button span { position: absolute; transform: translateX(-50%) translateY(-50%); white-space: nowrap; - /* prevent line breaks */ color: white; } @@ -412,21 +396,105 @@ a:hover .text-button span { } .hero-div>a:hover>span { - text-decoration: underline; color: var(--md-typeset-btn-color); + text-decoration: underline; } .heading-div { width: 100%; position: absolute; - transform: translateX(-50%); left: 50%; bottom: min(2vh, 3vw); + transform: translateX(-50%); pointer-events: none; color: #fff; } -/* code badge */ +/* Landing page CTAs */ +.rounded-button { + display: inline-flex; + align-items: center; + justify-content: center; + margin: 0 0.1em; + padding: 0.6em 1.3em; + border: none; + border-radius: 10em; + background-color: var(--md-typeset-btn-color); + color: var(--md-primary-fg-color) !important; + text-align: center; + text-decoration: none; + cursor: pointer; +} + +.rounded-button:hover { + background-color: var(--md-typeset-btn-hover-color); +} + +.rounded-button span { + margin: 0 0 0.07em; + white-space: nowrap; +} + +.rounded-button svg { + width: auto; + height: 1.2em; + margin-right: 0.5em; + fill: var(--md-primary-fg-color); +} + +.reading-media { + display: flex; + justify-content: center; + align-items: flex-end; + height: 32vw; +} + +.reading-media+p { + margin-top: 1em !important; +} + +.media-block { + height: 100%; + margin: 0 0.2em; +} + +.text-button { + width: auto; + margin: 2.7em auto; + color: var(--md-typeset-btn-color); + text-align: center; + text-decoration: none; +} + +.text-button span { + white-space: nowrap; +} + +.text-button svg { + display: inline-block; + width: auto; + height: 0.9em; + margin-left: 0.15em; + padding-top: 0.17em; + fill: var(--md-typeset-btn-color); + background-size: cover; +} + +a:hover .text-button span { + text-decoration: underline; +} + +.device-on-hover { + width: auto; + transition: transform 0.3s ease-in-out, filter 0.3s ease-in-out; +} + +a:hover .device-on-hover { + filter: drop-shadow(0 0 0.2rem rgba(0, 0, 0, 0.15)); + transform: scale(1.01); +} + +/* Landing page content blocks */ .code-badge { width: 100%; height: auto; @@ -434,11 +502,10 @@ a:hover .text-button span { } .code-badge img { - height: 1.07em; width: auto; + height: 1.07em; } -/* brief intro */ .intro-container { display: flex; align-items: center; @@ -455,14 +522,13 @@ a:hover .text-button span { .intro-text { flex-grow: 1; - /* fill the space */ display: flex; flex-direction: column; justify-content: center; - text-align: left; align-items: flex-start; width: fit-content; margin: 2em; + text-align: left; } .intro-text>div { @@ -471,6 +537,10 @@ a:hover .text-button span { margin: 0 auto; } +.intro-text svg path { + fill: #3b3b3b; +} + .endor-text { width: 50%; } @@ -480,7 +550,10 @@ a:hover .text-button span { font-weight: bold; } -/* contributors table */ +.home-div .intro-quote { + color: var(--md-default-fg-color--light) !important; +} + .profile-div { display: flex; flex-wrap: wrap; @@ -495,6 +568,11 @@ a:hover .text-button span { text-align: center; } +.profile-cell a:hover b, +.profile-cell a:focus-visible b { + text-decoration: underline; +} + .profile-img { width: 5em; border-radius: 50%; @@ -517,7 +595,37 @@ a:hover .text-button span { margin: 0 auto; } -/* Hide navigation */ +/* Embedded media */ +.video-container { + position: relative; + padding-bottom: 56.25%; + height: 0; +} + +.video-container iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.starfield { + position: absolute; + width: 100%; + height: 100%; + z-index: 0; + background-color: var(--hero-starfield-bg-color, transparent); +} + +.starfield-origin { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +/* Responsive adjustments */ @media screen and (max-width: 76.25em) { .section-content { max-width: 95vw; @@ -532,7 +640,6 @@ a:hover .text-button span { } } -/* mobile devices */ @media screen and (max-width: 60em) { .home-div { font-size: 0.75rem; @@ -571,209 +678,4 @@ a:hover .text-button span { flex: 1 1 30%; } } - -.video-container { - position: relative; - padding-bottom: 56.25%; - /* 16:9 */ - height: 0; -} - -.video-container iframe { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -/* starfield */ -.starfield { - position: absolute; - width: 100%; - height: 100%; - z-index: 0; - background-color: var(--hero-starfield-bg-color, transparent); -} - -.starfield-origin { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -/* Zensical-specific adjustments merged into the main stylesheet. */ -:root>* { - --md-accent-fg-color: var(--md-typeset-a-color); - --md-admonition-pythontutor-color: var(--md-code-bg-color); - --hello-algo-sidebar-width: 13rem; -} - -[data-md-color-scheme="slate"] { - --md-accent-fg-color: var(--md-typeset-a-color); - --md-admonition-pythontutor-color: var(--md-code-bg-color); - --md-body-bg-color: var(--md-default-bg-color); - --md-default-bg-color--light: rgb(30 30 30 / 0.8); -} - -[data-md-color-scheme="slate"] .md-typeset details.pythontutor[open]> :not(summary), -[data-md-color-scheme="slate"] .md-typeset details.pythontutor[open]> :not(summary) :is(p, li, strong, em, sub, sup, code, a) { - background-color: #f5f5f5; - color: #1d1d20; -} - -body { - background-color: var(--md-default-bg-color); -} - -html:has(body[data-md-color-scheme="slate"]) { - background-color: #1e1e1e; -} - -html:has(body[data-md-color-scheme="default"]) { - background-color: #ffffff; -} - -.home-div[data-md-color-scheme="default"], -.home-div[data-md-color-scheme="default"] h1, -.home-div[data-md-color-scheme="default"] h2, -.home-div[data-md-color-scheme="default"] h3, -.home-div[data-md-color-scheme="default"] h4, -.home-div[data-md-color-scheme="default"] h5, -.home-div[data-md-color-scheme="default"] h6 { - color: var(--md-default-fg-color); -} - -.home-div[data-md-color-scheme="slate"], -.home-div[data-md-color-scheme="slate"] h1, -.home-div[data-md-color-scheme="slate"] h2, -.home-div[data-md-color-scheme="slate"] h3, -.home-div[data-md-color-scheme="slate"] h4, -.home-div[data-md-color-scheme="slate"] h5, -.home-div[data-md-color-scheme="slate"] h6 { - color: var(--md-default-fg-color); -} - -.home-div .intro-quote { - color: var(--md-default-fg-color--light) !important; -} - -.reading-media+p { - margin-top: 1em !important; -} - -.md-typeset .admonition-title:before, -.md-typeset summary:before, -.md-typeset summary:after { - top: 50%; -} - -.md-typeset .admonition-title:before, -.md-typeset summary:before { - transform: translateY(-50%); -} - -.md-typeset summary:after { - transform: translateY(-50%) rotate(0deg); -} - -.md-typeset details[open]>summary:after { - transform: translateY(-50%) rotate(90deg); -} - -.md-nav__link[for] { - color: inherit !important; -} - -.md-nav__link[for].md-nav__link--active { - color: var(--md-accent-fg-color) !important; -} - -@media screen and (min-width: 76.25em) { - .md-grid { - max-width: calc(61rem + 2 * (var(--hello-algo-sidebar-width) - 12.1rem)); - } - - .md-sidebar--primary, - .md-sidebar--secondary { - width: var(--hello-algo-sidebar-width); - } - - [dir="ltr"] .md-sidebar__inner { - padding-right: calc(100% - (var(--hello-algo-sidebar-width) - 0.6rem)); - } - - [dir="rtl"] .md-sidebar__inner { - padding-left: calc(100% - (var(--hello-algo-sidebar-width) - 0.6rem)); - } -} - -.md-sidebar--primary .md-sidebar__scrollwrap { - scrollbar-color: var(--md-default-fg-color--lighter) transparent; -} - -.md-sidebar--primary .md-sidebar__scrollwrap::-webkit-scrollbar-thumb, -.md-sidebar--primary .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover { - background-color: var(--md-default-fg-color--lighter); -} - -.md-footer, -.md-footer__inner, -.md-footer-meta, -.md-footer__link, -.md-footer__link:hover { - background-color: var(--md-default-bg-color); -} - -.md-footer { - border-top: 0.05rem solid var(--md-default-fg-color--lightest); -} - -[data-md-color-scheme="slate"] .md-footer, -[data-md-color-scheme="slate"] .md-footer__inner, -[data-md-color-scheme="slate"] .md-footer-meta, -[data-md-color-scheme="slate"] .md-footer__link, -[data-md-color-scheme="slate"] .md-footer__link:hover { - background-color: var(--md-default-bg-color); -} - -.md-banner { - background-color: var(--md-default-bg-color); -} - -.md-typeset .admonition.pythontutor, -.md-typeset details.pythontutor, -.md-typeset .pythontutor>.admonition-title, -.md-typeset .pythontutor>summary { - background-color: var(--md-code-bg-color); -} - -.md-typeset .pythontutor>.admonition-title::before, -.md-typeset .pythontutor>summary::before, -.md-typeset .pythontutor>summary::after { - top: 50%; -} - -.md-typeset .pythontutor>.admonition-title::before, -.md-typeset .pythontutor>summary::before { - transform: translateY(-50%); -} - -.md-typeset .pythontutor>summary::after { - transform: translateY(-50%) rotate(0deg); -} - -.md-typeset details[open].pythontutor>summary::after { - transform: translateY(-50%) rotate(90deg); -} - -.md-typeset a:not(.md-button) { - text-decoration: none; -} - -.md-typeset a:not(.md-button):hover, -.md-typeset a:not(.md-button):focus-visible { - text-decoration: underline; -} -/*! update cache: 20260331044513 */ +/*! update cache: 20260331053206 */ diff --git a/ja/assets/covers/chapter_appendix.jpg b/ja/assets/covers/chapter_appendix.jpg index 01e4dd55268a1a79b477be63e4f726a860648c0f..1c4cacacb2fe273aaf1222177dc5cf729eae2019 100644 GIT binary patch delta 135166 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 AhyVZp delta 134107 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⋘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�^zb$hFS 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 diff --git a/ja/assets/covers/chapter_array_and_linkedlist.jpg b/ja/assets/covers/chapter_array_and_linkedlist.jpg index 4cf13ceb3243b51a69e4c4cf74bddc08de2b9574..5174b20350cc02cfb33fc366147188eb7b639fbd 100644 GIT binary patch delta 128344 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 G0%w)+LS#M2L$kKt4P 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

ozczNuX<|uTg!S&2sboDvOk59~<|NC(} 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

I5w<2Ob%|_SbT7fi=g%R!^_@fQnfJ|9 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

N1MyK4#jJ3-^r{#PTHGAaKd)_p#?5f7YFS^2A{r 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 z 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 diff --git a/ja/assets/covers/chapter_backtracking.jpg b/ja/assets/covers/chapter_backtracking.jpg index 6aa3208d2bdd78d07130290d4e3072c7beb611aa..4ea0ffc10ba2247f65981b0a086fa5d33151b453 100644 GIT binary patch literal 130797 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

{-}?mH{{@(hP#S3t`Cc)D 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 MNIp@02>m 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∓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

|(^_(dUKz;&N!z-X(*BqxAIbbH=s`dJ6yTA|~r1 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

Z}z+~xT%p4dZu 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 z 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

oq;Bv|r^z!(CRo2czb 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 literal 144041 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Ð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 Z#&O!Lw;*P%nT^~i$n~aV(YVkD|JhJj&9DFf diff --git a/ja/assets/covers/chapter_divide_and_conquer.jpg b/ja/assets/covers/chapter_divide_and_conquer.jpg index 0b3b90eead52969f9264e263529ac6c1f20e6446..e95d52aec77ecc98e5d75ce2611696636a5c17ea 100644 GIT binary patch delta 106052 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

diff --git a/ru/chapter_array_and_linkedlist/linked_list/index.html b/ru/chapter_array_and_linkedlist/linked_list/index.html index ae926a13f..40f492544 100644 --- a/ru/chapter_array_and_linkedlist/linked_list/index.html +++ b/ru/chapter_array_and_linkedlist/linked_list/index.html @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1183,7 +1183,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1205,7 +1205,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1464,7 +1464,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1746,7 +1746,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1768,7 +1768,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1846,7 +1846,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1941,7 +1941,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1963,7 +1963,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -2041,7 +2041,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2125,7 +2125,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2288,7 +2288,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2379,7 +2379,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2401,7 +2401,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2451,7 +2451,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2507,7 +2507,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2646,7 +2646,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2674,7 +2674,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2702,7 +2702,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2730,7 +2730,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2935,7 +2935,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2963,7 +2963,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3268,7 +3268,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3296,7 +3296,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3573,7 +3573,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3714,7 +3714,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3826,7 +3826,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке diff --git a/ru/chapter_array_and_linkedlist/list/index.html b/ru/chapter_array_and_linkedlist/list/index.html index 84bd205c7..d7b1e2ca7 100644 --- a/ru/chapter_array_and_linkedlist/list/index.html +++ b/ru/chapter_array_and_linkedlist/list/index.html @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1183,7 +1183,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1205,7 +1205,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1453,7 +1453,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1735,7 +1735,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1757,7 +1757,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1835,7 +1835,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1930,7 +1930,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1952,7 +1952,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -2030,7 +2030,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2114,7 +2114,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2277,7 +2277,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2368,7 +2368,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2390,7 +2390,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2440,7 +2440,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2496,7 +2496,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2635,7 +2635,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2663,7 +2663,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2691,7 +2691,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2719,7 +2719,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2924,7 +2924,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2952,7 +2952,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3257,7 +3257,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3285,7 +3285,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3562,7 +3562,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3703,7 +3703,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3815,7 +3815,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -6663,7 +6663,7 @@ aria-label="Нижний колонтитул"

&=5 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