İçeriğe atla
ΣDSA Patterns
Menü
Dil

Dil

Mülakatlar için TypeScript

Deyimler, koleksiyonlar, ListNode/TreeNode ve şablonların gerçekten çalıştırdığı heap. Node 24.

Sitenin şablonları TypeScript’tir ve JavaScript number olarak çalışır. CI’daki Node 24. Aşağıdaki her çit, silinebilir TypeScript’ten bir betiktir: node --experimental-strip-types çalıştırır.

number bir float64. Tam sayılar 2**53 - 1 (Number.MAX_SAFE_INTEGER) kadar tamdır. Bitwise operatörler yalnızca int32 görür. O kısım bit-manipülasyonu kalıp sayfasında.

Bölme ve kalan

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

check(Math.trunc(-3 / 2) === -1);
check(Math.floor(-3 / 2) === -2);
check(-3 % 2 === -1);
check(2 ** 53 === 2 ** 53 + 1);
check(Number.isSafeInteger(2 ** 53 - 1));
check(!Number.isSafeInteger(2 ** 53));

Math.trunc sıfıra gider. Math.floor negatif sonsuza gider; Python // de öyle. Kalan, bölünenin işaretini alır. Tamsayı bölme operatörü yoktur. >> int32 üzerinde aritmetik kaydırmadır; negatif veya 2**31 - 1 ötesi için yanlış araçtır.

Dizgiler

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

const s = "ab,cd";
check(s[0] === "a" && s.at(-1) === "d");
check(s.slice(1, 4) === "b,c");
check([...s].reverse().join("") === "dc,ba");
check(s.split(",")[0] === "ab" && s.split(",")[1] === "cd");
check(["ab", "cd"].join(" ") === "ab cd");
check(s.replace("ab", "xy") === "xy,cd");
check(s.startsWith("ab") && s.endsWith("cd"));
check(s.indexOf("z") === -1);
check(s.includes("cd"));
check("A".charCodeAt(0) === 65 && String.fromCharCode(65) === "A");
check("ada".repeat(2) === "adaada");

Sondan sonra s[i] undefined. s.at(-1) son karakter. Alt dizgi yoksa indexOf -1 döner. Ayırıcısız split her UTF-16 biriminde böler. Dizgiler değişmez; replace yeni dizgi döner ve global regex yoksa ilk eşleşmeyi değiştirir.

Şablon dizgi ${expr} ile interpolasyon yapar.

Diziler

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}
function same(a: readonly number[], b: readonly number[]): boolean {
  return a.length === b.length && a.every((v, i) => v === b[i]);
}

const lexical = [10, 2, 1].sort();
check(same(lexical, [1, 10, 2]));
const numeric = [10, 2, 1].sort((a, b) => a - b);
check(same(numeric, [1, 2, 10]));

const row = [0, 0];
const shared = [row, row];
shared[0]![0] = 1;
check(shared[1]![0] === 1);

const rows = Array.from({ length: 2 }, () => [0, 0]);
rows[0]![0] = 1;
check(rows[1]![0] === 0);

const nums = [1, 2, 3];
nums.push(4);
check(nums.pop() === 4);
check(nums.shift() === 1);
check(nums.slice(0, 1)[0] === 2);

Karşılaştırıcısız sort string forma göre sıralar; 10, 2’den önce gelir. Artan sayılar için (a, b) => a - b ver. Modern motorlar eşit elemanların orijinal sırasını korur.

Array.from({ length: n }, () => []) her satıra taze bir iç dizi kurar. Tek bir dizi nesnesiyle fill o nesneyi her yuvaya koyar.

push ve pop yığın uçlarıdır. shift ve unshift sonraki her elemanı kaydırır. Queue-deque şablonu indeksleri bir dizide tutar ve önde shift çağırır.

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}
function same(a: readonly number[], b: readonly number[]): boolean {
  return a.length === b.length && a.every((v, i) => v === b[i]);
}

function maxSlidingWindow(nums: number[], k: number): number[] {
  const dq: number[] = [];
  const res: number[] = [];
  for (let i = 0; i < nums.length; i++) {
    while (dq.length && dq[0]! <= i - k) dq.shift();
    while (dq.length && nums[dq[dq.length - 1]!]! <= nums[i]!) dq.pop();
    dq.push(i);
    if (i >= k - 1) res.push(nums[dq[0]!]!);
  }
  return res;
}

check(same(maxSlidingWindow([1, 3, -1, -3, 5, 3, 6, 7], 3), [3, 3, 5, 5, 6, 7]));

nums[i]! tip denetleyicisine indeksin var olduğunu söyler. Çalışma anında kontrol etmez. Şablonlardaki !, bir uzunluk testinden veya o yuvayı dolduran bir yazmadan sonra durur.

const bağlamayı sabitler. İşaret ettiği dizi veya nesne hâlâ değişebilir.

Karşılaştırma ve eksik değerler

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

check(0 == false);
check(!(0 === false));
check((0 || 5) === 5);
check((0 ?? 5) === 0);
check(("" || "default") === "default");
check(("" ?? "default") === "");
check((null as number | null)?.toFixed === undefined);
check([1, 2, 3].at(9) === undefined);

const bag: Record<string, number> = {};
bag[1] = 4;
check(Object.keys(bag)[0] === "1");

=== zorlamadan karşılaştırır. == zorlar, bu yüzden 0 == false doğrudur. || 0 ve "" değerlerini eksik sayar. ?? yalnızca null ve undefined yerine geçer.

Düz bir nesnenin anahtarları string’dir. Map 1 ile "1"’i ayrı tutar.

?. null veya undefined üzerinde kısa devre yapar. Aralık dışı indeks istisna değil, undefined.

Map, Set ve two-sum

Bu, hashing şablonunun şeklidir.

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}
function same(a: readonly number[], b: readonly number[]): boolean {
  return a.length === b.length && a.every((v, i) => v === b[i]);
}

function twoSum(nums: number[], target: number): number[] {
  const seen = new Map<number, number>();
  for (let i = 0; i < nums.length; i++) {
    const need = target - nums[i]!;
    if (seen.has(need)) return [seen.get(need)!, i];
    seen.set(nums[i]!, i);
  }
  throw new Error("no solution");
}

check(same(twoSum([2, 7, 11, 15], 9), [0, 1]));

const uniq = new Set([1, 1, 2]);
check(uniq.size === 2 && uniq.has(2));
uniq.add(3);
uniq.delete(1);
check(!uniq.has(1) && uniq.has(3));

Anahtar yoksa Map.get undefined döner. Üyelik testi has. Set’in indeksi yoktur; for...of ile dolaş. Ekleme sırası korunur.

Döngüler ve kontrol akışı

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}
function same(a: readonly number[], b: readonly number[]): boolean {
  return a.length === b.length && a.every((v, i) => v === b[i]);
}

const seen: number[] = [];
for (const [i, value] of ["a", "b"].entries()) seen.push(i);
check(same(seen, [0, 1]));

const keys: string[] = [];
for (const k in { a: 1, b: 2 }) keys.push(k);
check(keys[0] === "a" && keys[1] === "b");

let found = -1;
for (const value of [1, 3, 4]) {
  if (value % 2 === 0) {
    found = value;
    break;
  }
}
check(found === 4);

const doubled = [1, 2, 3].map((x) => x * 2);
check(same(doubled, [2, 4, 6]));
check([1, 2, 3].filter((x) => x % 2 === 1).length === 2);
check([1, 2, 3].reduce((a, b) => a + b, 0) === 6);

for...of değerleri gezer. for...in enumerable anahtarları gezer, özel prototipte kalıtılanlar dahil; nesne veri ise Object.keys veya Map yeğle. entries() indeks-plus-değer biçimidir.

map / filter / reduce tahsis eder. İç gövde algoritmaysa for döngüsü maliyeti görünür tutar.

Ternary cond ? a : b. switch === ile karşılaştırır.

Fonksiyonlar

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

function add(a: number, b = 0): number {
  return a + b;
}
check(add(1) === 1 && add(1, 2) === 3);

const times = (n: number) => (x: number) => x * n;
check(times(3)(4) === 12);

function rest(first: number, ...more: number[]): number {
  return first + more.reduce((a, b) => a + b, 0);
}
check(rest(1, 2, 3) === 6);

const fns = [];
for (var i = 0; i < 3; i++) fns.push(() => i);
check(fns[0]!() === 3 && fns[1]!() === 3);

const bound: number[] = [];
for (let j = 0; j < 3; j++) bound.push(j);
check(bound[0] === 0 && bound[2] === 2);

Varsayılan çağrıda hesaplanır. Python [] varsayılanı gibi paylaşılan değişken varsayılan yoktur.

var fonksiyon kapsamlıdır; for (var …) döngüsü üzerindeki closure son değeri görür. let ve const blok kapsamlıdır. Ok fonksiyonları this’i miras alır; function metodunun kendi this’i vardır.

Sınıflar ve this

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

class Counter {
  private n: number;
  constructor(n = 0) {
    this.n = n;
  }
  inc(): number {
    return ++this.n;
  }
  get value(): number {
    return this.n;
  }
}

const c = new Counter();
check(c.inc() === 1 && c.value === 1);

class NamedCounter extends Counter {
  readonly name: string;
  constructor(name: string) {
    super(0);
    this.name = name;
  }
}
check(new NamedCounter("ticks").name === "ticks");

extends this’ten önce super(...) ister. Sınıf alanı oku inc = () => ++this.n this’i örneğe bağlar; prototip metodu bağlamaz. Parametre özellikleri (constructor(private n = 0)) geçerli TypeScript’tir; Node’un strip-types yükleyicisi kabul etmez.

Destructure

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

let a = 1, b = 2;
[a, b] = [b, a];
check(a === 2 && b === 1);

const [head, ...tail] = [1, 2, 3];
check(head === 1 && tail[0] === 2 && tail[1] === 3);

const point = { x: 3, y: 4 };
const { x, y: yy } = point;
check(x === 3 && yy === 4);

function pair([left, right]: [number, number]): number {
  return left + right;
}
check(pair([2, 5]) === 7);

İki bağlamayı [a, b] = [b, a] ile takas et. Dizide rest kalan elemanlardır. Alanı y: yy ile yeniden adlandır. İç içe kalıplar parametrelerde de çalışır.

Matematik ve BigInt

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

check(Math.max(1, 4, 2) === 4);
check(Math.min(...[1, 4, 2]) === 1);
check(Math.abs(-3) === 3);
check(Math.hypot(3, 4) === 5);
check(Number.isInteger(3) && !Number.isInteger(3.1));
check(Number.parseInt("08", 10) === 8);
check(1e9 + 7 === 1_000_000_007);

const wide = 2n ** 53n + 1n;
check(wide + 1n === 2n ** 53n + 2n);
check(BigInt(Number.MAX_SAFE_INTEGER) === 2n ** 53n - 1n);

Argümansız Math.max -Infinity. Boş olmayan diziyi spread et. Radix’siz parseInt bazı ortamlarda baştaki 0’ı octal sayar; 10 ver.

BigInt ayrı bir tiptir. number ile karıştırınca motor fırlatır. number üzerinde bitwise hâlâ int32; kaydırma 31’in ötesindeki bitleri tutacaksa BigInt kullan.

Heap

Standart kütüphanede heap yoktur. Top-K şablonu her eklemede k boyutlu diziyi sıralar. Push ve pop’un ait olduğu yapı ikili heap’tir. Bu bir min-heap. Max-heap için, güvenli tamsayı aralığında, negatifi it.

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

class MinHeap {
  private a: number[] = [];

  push(x: number): void {
    this.a.push(x);
    this.up(this.a.length - 1);
  }

  pop(): number {
    const a = this.a;
    if (a.length === 0) throw new Error("empty");
    const top = a[0]!;
    const last = a.pop()!;
    if (a.length > 0) {
      a[0] = last;
      this.down(0);
    }
    return top;
  }

  peek(): number {
    if (this.a.length === 0) throw new Error("empty");
    return this.a[0]!;
  }

  get size(): number {
    return this.a.length;
  }

  private up(i: number): void {
    const a = this.a;
    while (i > 0) {
      const p = (i - 1) >> 1;
      if (a[p]! <= a[i]!) break;
      [a[p], a[i]] = [a[i]!, a[p]!];
      i = p;
    }
  }

  private down(i: number): void {
    const a = this.a;
    for (;;) {
      let smallest = i;
      const l = i * 2 + 1;
      const r = l + 1;
      if (l < a.length && a[l]! < a[smallest]!) smallest = l;
      if (r < a.length && a[r]! < a[smallest]!) smallest = r;
      if (smallest === i) break;
      [a[smallest], a[i]] = [a[i]!, a[smallest]!];
      i = smallest;
    }
  }
}

const heap = new MinHeap();
for (const x of [3, 1, 4, 2]) heap.push(x);
check(heap.pop() === 1 && heap.pop() === 2 && heap.pop() === 3 && heap.peek() === 4);

function findKthLargest(nums: number[], k: number): number {
  const h = new MinHeap();
  for (const x of nums) {
    h.push(x);
    if (h.size > k) h.pop();
  }
  return h.peek();
}

check(findKthLargest([3, 2, 1, 5, 6, 4], 2) === 5);

İkili arama

İkili arama şablonu lo + ((hi - lo) >> 1) ile yarılar. İki uç da negatif olmayan indekstir; kaydırma pozitif int32 aralığında kalır.

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

function binarySearch(nums: number[], target: number): number {
  let lo = 0;
  let hi = nums.length;
  while (lo < hi) {
    const mid = lo + ((hi - lo) >> 1);
    if (nums[mid]! < target) lo = mid + 1;
    else hi = mid;
  }
  return lo < nums.length && nums[lo] === target ? lo : -1;
}

check(binarySearch([1, 3, 3, 7], 3) === 1);
check(binarySearch([1, 3, 3, 7], 4) === -1);
check(binarySearch([], 1) === -1);

Bağlı listeler

Bağlı liste şablonu bu düğüm artı reverse ve dummy-head birleştirmesidir.

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val = 0, next: ListNode | null = null) {
    this.val = val;
    this.next = next;
  }
}

function reverseList(head: ListNode | null): ListNode | null {
  let prev: ListNode | null = null;
  let cur = head;
  while (cur) {
    const nxt = cur.next;
    cur.next = prev;
    prev = cur;
    cur = nxt;
  }
  return prev;
}

function toArray(head: ListNode | null): number[] {
  const out: number[] = [];
  while (head) {
    out.push(head.val);
    head = head.next;
  }
  return out;
}

const a = new ListNode(1, new ListNode(2, new ListNode(3)));
check(toArray(reverseList(a)).join(",") === "3,2,1");

while (cur) ile yürü. Dummy head, birleştirme döngüsünün ilk düğümü özel saymasını engeller. Boş liste null.

Ağaçlar

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

class TreeNode {
  val: number;
  left: TreeNode | null;
  right: TreeNode | null;
  constructor(val = 0, left: TreeNode | null = null, right: TreeNode | null = null) {
    this.val = val;
    this.left = left;
    this.right = right;
  }
}

function maxDepth(root: TreeNode | null): number {
  if (!root) return 0;
  return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}

function levelOrder(root: TreeNode | null): number[][] {
  if (!root) return [];
  const res: number[][] = [];
  const q: TreeNode[] = [root];
  while (q.length) {
    const size = q.length;
    const level: number[] = [];
    for (let i = 0; i < size; i++) {
      const n = q.shift()!;
      level.push(n.val);
      if (n.left) q.push(n.left);
      if (n.right) q.push(n.right);
    }
    res.push(level);
  }
  return res;
}

const root = new TreeNode(1, new TreeNode(2), new TreeNode(3, new TreeNode(4)));
check(maxDepth(root) === 3);
check(levelOrder(root).map((row) => row.join(",")).join("|") === "1|2,3|4");

left ve right üzerinde özyineleme DFS şablonudur. Seviye sırası q.length anlık görüntüsü alır; böylece yeni itilen çocuklar mevcut seviyeyi bozmaz.

Graflar

Graf-DFS şablonu number[][] komşuluk kurar. Izgara flood fill [r, c] hücrelerinin yığınıdır.

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

function countComponents(n: number, edges: [number, number][]): number {
  const adj: number[][] = Array.from({ length: n }, () => []);
  for (const [a, b] of edges) {
    adj[a]!.push(b);
    adj[b]!.push(a);
  }
  const visited = new Array<boolean>(n).fill(false);
  let count = 0;
  for (let v = 0; v < n; v++) {
    if (!visited[v]) {
      dfs(v);
      count++;
    }
  }
  return count;

  function dfs(v: number) {
    visited[v] = true;
    for (const u of adj[v]!) if (!visited[u]) dfs(u);
  }
}

check(countComponents(4, [[0, 1], [2, 3]]) === 2);

function floodFill(
  image: number[][], sr: number, sc: number, newColor: number,
): number[][] {
  const orig = image[sr]![sc]!;
  if (orig === newColor) return image;
  const rows = image.length, cols = image[0]!.length;
  const stack: [number, number][] = [[sr, sc]];
  while (stack.length) {
    const [r, c] = stack.pop()!;
    if (r < 0 || c < 0 || r >= rows || c >= cols || image[r]![c] !== orig) continue;
    image[r]![c] = newColor;
    stack.push([r + 1, c], [r - 1, c], [r, c + 1], [r, c - 1]);
  }
  return image;
}

const filled = floodFill([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 2);
check(filled[0]!.join(",") === "2,2,2" && filled[2]!.join(",") === "2,0,1");

orig === newColor aynı rengi sonsuz yeniden yazmayı kesen erken dönüştür.

Union-Find

Path compression artı rank’e göre birleştirme. union, iki düğümün farklı bileşenlerde olup olmadığını döner.

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

class UnionFind {
  parent: number[];
  rank: number[];
  components: number;
  constructor(n: number) {
    this.parent = Array.from({ length: n }, (_, i) => i);
    this.rank = new Array(n).fill(0);
    this.components = n;
  }
  find(x: number): number {
    if (this.parent[x] !== x) this.parent[x] = this.find(this.parent[x]!);
    return this.parent[x]!;
  }
  union(a: number, b: number): boolean {
    let ra = this.find(a), rb = this.find(b);
    if (ra === rb) return false;
    if (this.rank[ra]! < this.rank[rb]!) [ra, rb] = [rb, ra];
    this.parent[rb] = ra;
    if (this.rank[ra] === this.rank[rb]) this.rank[ra]!++;
    this.components--;
    return true;
  }
}

const uf = new UnionFind(4);
check(uf.union(0, 1) && uf.union(2, 3));
check(!uf.union(0, 1));
check(uf.components === 2);

Trie

function check(ok: boolean): void {
  if (!ok) throw new Error("check failed");
}

class TrieNode {
  children = new Map<string, TrieNode>();
  isWord = false;
}
class Trie {
  root = new TrieNode();
  insert(word: string): void {
    let node = this.root;
    for (const ch of word) {
      if (!node.children.has(ch)) node.children.set(ch, new TrieNode());
      node = node.children.get(ch)!;
    }
    node.isWord = true;
  }
  search(word: string): boolean {
    const node = this.walk(word);
    return Boolean(node?.isWord);
  }
  startsWith(prefix: string): boolean {
    return this.walk(prefix) != null;
  }
  private walk(s: string): TrieNode | null {
    let node: TrieNode | null = this.root;
    for (const ch of s) {
      if (!node!.children.has(ch)) return null;
      node = node!.children.get(ch)!;
    }
    return node;
  }
}

const t = new Trie();
t.insert("apple");
check(t.search("apple") && !t.search("app"));
check(t.startsWith("app"));

search isWord ister. startsWith yalnızca yürüyüşün hayatta kalmasını ister.