0Pricing
Coding Interview Prep · Lekcja

Próbna rozmowa techniczna na czas: problemy łatwe i średnie

Rozwiązać trzy problemy w ciągu 45 minut, werbalizować tok rozumowania tak jak podczas prawdziwej rozmowy oraz przeanalizować później optymalne rozwiązania

Próbna rozmowa techniczna na czas: problemy łatwe i średnie to bezpłatna lekcja Coding Interview Prep na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Coding Interview Prep, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Coding Interview Prep zawiera 4 lekcji w sumie.

Jak korzystać z tej próbnej rozmowy rekrutacyjnej

Ta lekcja symuluje prawdziwą sesję rozmowy rekrutacyjnej dotyczącej programowania. W przypadku każdego zadania należy: (1) przeczytać je raz, (2) rozpoznać wzorzec w ciągu 60 sekund, (3) przedstawić swoje podejście i jego złożoność, (4) napisać rozwiązanie oraz (5) przetestować je na przykładach. Proszę ustawić minutnik. Rozwiązanie łatwego zadania powinno zająć 10–15 minut, a zadania średniego poziomu 20–25 minut.

Proszę nie zaglądać z wyprzedzeniem do rozwiązania — mija się to z celem. Jeśli utkną Państwo po 5 minutach, proszę ponownie przeczytać treść zadania i poszukać słowa sygnałowego ujawniającego wzorzec (posortowane? minimum? wszystkie kombinacje? podtablica?). Umiejętność samodzielnego wyjścia z impasu jest równie ważna jak umiejętność szybkiego rozwiązywania zadań.

# Mock interview timer simulation
import time

class InterviewTimer:
    def __init__(self, total_minutes):
        self.total = total_minutes * 60
        self.start = None

    def begin(self, problem_name):
        self.start = time.time()
        print(f'TIMER STARTED: {problem_name}')
        print(f'You have {self.total//60} minutes. Go!')

    def checkpoint(self, label):
        if self.start:
            elapsed = time.time() - self.start
            remaining = self.total - elapsed
            print(f'[{label}] Elapsed: {elapsed:.0f}s, Remaining: {remaining:.0f}s')

# Usage in real practice:
timer = InterviewTimer(15)  # 15-minute easy problem
timer.begin('Two Sum')
time.sleep(1)
timer.checkpoint('Identified pattern')

Łatwe zadanie 1: poprawne nawiasy

Problem: Mając ciąg zawierający wyłącznie '(', ')', '{', '}', '[', ']', należy określić, czy ciąg wejściowy jest poprawny. Ciąg jest poprawny, jeśli każdy nawias otwierający zostaje zamknięty nawiasem tego samego typu i we właściwej kolejności.

Sygnał: Pary dopasowanych nawiasów, znaczenie kolejności, ostatni otwarty nawias musi zostać zamknięty jako pierwszy → Stos. Otwierające nawiasy należy umieszczać na stosie, a przy zamykających zdejmować element ze stosu i sprawdzać jego typ. Jeśli stos jest pusty podczas próby zdjęcia elementu albo na końcu pozostają w nim elementy, ciąg jest niepoprawny. Czas O(n), pamięć O(n).

def is_valid(s):
    stack = []
    matching = {')': '(', '}': '{', ']': '['}

    for char in s:
        if char in '({[':
            stack.append(char)
        else:
            if not stack or stack[-1] != matching[char]:
                return False
            stack.pop()
    return len(stack) == 0

# Test cases
test_cases = [
    ('()', True),
    ('()[]{}'  , True),
    ('(]', False),
    ('([)]', False),
    ('{[]}', True),
    ('', True),        # empty string is valid
    ('(((', False),    # unmatched opens
    (')]', False),     # close without open
]
for s, expected in test_cases:
    result = is_valid(s)
    status = 'PASS' if result == expected else 'FAIL'
    print(f'{status}: is_valid({repr(s)}) = {result} (expected {expected})')

Łatwe zadanie 2: najlepszy moment na kupno i sprzedaż akcji

Problem: Mając tablicę prices, w której prices[i] oznacza cenę akcji w dniu i, należy znaleźć maksymalny zysk z jednego kupna i jednej sprzedaży (kupno musi nastąpić przed sprzedażą). Jeśli osiągnięcie zysku nie jest możliwe, należy zwrócić 0.

Sygnał: Maksymalna różnica, w której lewa wartość musi poprzedzać prawą → Podczas przechodzenia od lewej do prawej należy śledzić bieżące minimum. Każdego dnia potencjalny zysk wynosi current_price - min_so_far. Należy aktualizować maksymalny zysk. Złożoność tego rozwiązania to O(n)/O(1); jest to szczególny przypadek algorytmu Kadane'a.

def max_profit(prices):
    if not prices:
        return 0
    min_price = float('inf')
    max_profit = 0

    for price in prices:
        if price < min_price:
            min_price = price
        elif price - min_price > max_profit:
            max_profit = price - min_price
    return max_profit

# Test cases
test_cases = [
    ([7, 1, 5, 3, 6, 4], 5),   # buy at 1, sell at 6
    ([7, 6, 4, 3, 1], 0),      # monotonically decreasing: no profit
    ([2, 4, 1], 2),             # buy at 2, sell at 4
    ([1], 0),                   # single price: no transaction possible
    ([3, 3, 3], 0),             # flat: no profit
]
for prices, expected in test_cases:
    result = max_profit(prices)
    status = 'PASS' if result == expected else 'FAIL'
    print(f'{status}: max_profit({prices}) = {result} (expected {expected})')

Średnio trudne zadanie 1: suma trzech elementów

Problem: Mając tablicę, należy znaleźć wszystkie unikatowe trójki elementów, których suma wynosi zero. Rozwiązanie nie może zawierać powtarzających się trójek.

Wzorzec: Dwa wskaźniki rozszerzone do trzech elementów. Należy posortować tablicę. Dla każdego elementu nums[i] należy użyć dwóch wskaźników left = i+1, right = n-1, aby znaleźć pary sumujące się do -nums[i]. Duplikaty należy pomijać, przesuwając wskaźnik za identyczne wartości. Czas O(n²), pamięć O(1), bez uwzględniania danych wyjściowych. Sortowanie ułatwia obsługę duplikatów.

def three_sum(nums):
    nums.sort()
    result = []
    n = len(nums)

    for i in range(n - 2):
        # Skip duplicate values for the first element
        if i > 0 and nums[i] == nums[i - 1]:
            continue
        left, right = i + 1, n - 1
        while left < right:
            total = nums[i] + nums[left] + nums[right]
            if total == 0:
                result.append([nums[i], nums[left], nums[right]])
                while left < right and nums[left] == nums[left + 1]:
                    left += 1      # skip duplicate lefts
                while left < right and nums[right] == nums[right - 1]:
                    right -= 1     # skip duplicate rights
                left += 1; right -= 1
            elif total < 0:
                left += 1
            else:
                right -= 1
    return result

print(three_sum([-1, 0, 1, 2, -1, -4]))  # [[-1,-1,2],[-1,0,1]]
print(three_sum([0, 0, 0, 0]))            # [[0,0,0]]
print(three_sum([]))                       # []
print(three_sum([1, 2, -2, -1]))           # []

Zadanie o średnim poziomie trudności 2: najdłuższy podciąg bez powtarzających się znaków

Problem: Dany jest napis. Proszę znaleźć długość najdłuższego podciągu, w którym nie powtarza się żaden znak.

Wzorzec: Przesuwne okno ze zbiorem (lub słownikiem ostatnich pozycji). Należy utrzymywać okno [left, right]. Proszę rozszerzać je w prawo, dodając kolejne znaki. Jeśli znak się powtarza (jest już w oknie), należy zmniejszać okno od lewej strony, aż duplikat zostanie usunięty. Proszę śledzić największy napotkany rozmiar okna. Czas O(n), pamięć O(min(n, alphabet_size)).

def length_of_longest_substring(s):
    char_index = {}    # character -> last seen index
    left = 0
    max_len = 0

    for right, char in enumerate(s):
        if char in char_index and char_index[char] >= left:
            left = char_index[char] + 1  # shrink window past duplicate
        char_index[char] = right
        max_len = max(max_len, right - left + 1)
    return max_len

# Test cases
test_cases = [
    ('abcabcbb', 3),   # 'abc'
    ('bbbbb', 1),       # 'b'
    ('pwwkew', 3),      # 'wke'
    ('', 0),            # empty string
    ('au', 2),          # full string
    ('dvdf', 3),        # 'vdf' (skip the first d)
]
for s, expected in test_cases:
    result = length_of_longest_substring(s)
    status = 'PASS' if result == expected else 'FAIL'
    print(f'{status}: len_longest({repr(s)}) = {result} (expected {expected})')

Zadanie o średnim poziomie trudności 3: rozmiana monet

Problem: Dany jest zbiór nominałów monet oraz kwota docelowa. Proszę znaleźć minimalną liczbę monet potrzebnych do uzyskania tej kwoty. Jeśli jest to niemożliwe, należy zwrócić -1.

Wzorzec: Klasyczne programowanie dynamiczne 1D (wariant problemu nieograniczonego plecaka). dp[i] = minimalna liczba monet potrzebna do uzyskania kwoty i. Proszę zainicjalizować dp[0] = 0, a wszystkie pozostałe wartości ustawić na nieskończoność. Dla każdej kwoty od 1 do kwoty docelowej należy wypróbować wszystkie nominały monet. Dla każdej poprawnej monety obowiązuje dp[i] = min(dp[i], dp[i - coin] + 1). Czas O(amount × len(coins)), pamięć O(amount).

def coin_change(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0   # 0 coins to make amount 0

    for i in range(1, amount + 1):
        for coin in coins:
            if coin <= i and dp[i - coin] + 1 < dp[i]:
                dp[i] = dp[i - coin] + 1

    return dp[amount] if dp[amount] != float('inf') else -1

# Test cases
test_cases = [
    ([1, 5, 11], 15, 3),      # 11+1+1+1+1... wait: 11+1+1+1+1=5 coins? No: 5+5+5=3
    ([2], 3, -1),              # impossible (only even coins)
    ([1], 0, 0),               # 0 coins for amount 0
    ([1, 2, 5], 11, 3),        # 5+5+1
    ([186, 419, 83, 408], 6249, 20),  # stress test
]
for coins, amount, expected in test_cases:
    result = coin_change(coins, amount)
    status = 'PASS' if result == expected else 'FAIL'
    print(f'{status}: coin_change({coins}, {amount}) = {result} (expected {expected})')

Proces rozwiązywania zadań pod presją czasu

Gdy kończy się czas, proszę nadać priorytet następującym kwestiom, w tej kolejności: (1) działające rozwiązanie brute-force z poprawnym wynikiem zamiast nieukończonego rozwiązania optymalnego, (2) widoczne uwzględnienie przypadków brzegowych, (3) napisanie czystego i czytelnego kodu zamiast sprytnych jednolinijkowców. Osoby rekrutujące wolą przejrzyste rozwiązanie O(n²), które przechodzi wszystkie przypadki testowe, niż rozwiązanie O(n) z trudnym do zauważenia błędem.

Jeśli okaże się, że rozwiązanie O(n²) jest niepoprawne, proszę go nie porzucać w połowie — należy je dokończyć, przetestować, a następnie zaproponować optymalizację, jeśli pozostanie na to czas. Częściowo napisane rozwiązanie optymalne jest oceniane gorzej niż kompletne, lecz nieoptymalne.

# Priority order when time runs out
priority = [
    ('First priority',  'Correct brute-force that passes all test cases'),
    ('Second priority', 'Optimal solution with bugs is WORSE than suboptimal correct'),
    ('Third priority',  'Edge cases handled visibly (empty input, single element, negatives)'),
    ('Fourth priority', 'Clean variable names and readable code'),
    ('Fifth priority',  'Add complexity statement as a comment at the top'),
]
print('Under time pressure, prioritise:')
for priority_level, desc in priority:
    print(f'  {priority_level}: {desc}')

# Adding complexity as a comment
def two_sum_commented(nums, target):
    # Time: O(n), Space: O(n)
    seen = {}
    for i, n in enumerate(nums):
        complement = target - n
        if complement in seen:
            return [seen[complement], i]
        seen[n] = i
    return []

Przegląd rozwiązania: pięć pytań

Zanim powie Pan/Pani „skończyłem/am”, proszę zadać sobie te pięć pytań:

  1. Czy rozwiązanie obsługuje puste dane wejściowe? [], '', None, n=0
  2. Czy rozwiązanie obsługuje pojedynczy element? Tablice o rozmiarze 1, drzewa z jednym węzłem
  3. Czy rozwiązanie obsługuje elementy o jednakowych wartościach? [5, 5, 5, 5], 'aaaa'
  4. Czy rozwiązanie obsługuje wartości minimalne i maksymalne? Liczby ujemne, bardzo duże liczby całkowite, 0
  5. Czy została określona złożoność czasowa i pamięciowa? Notacja Big-O z krótkim uzasadnieniem

Te pięć kontroli pozwala wykryć większość błędów w rozwiązaniach zadań rekrutacyjnych. Osoby rekrutujące oczekują, że kandydaci samodzielnie przetestują swoje rozwiązania — nie powiedzą Państwu, że rozwiązanie zawiera błąd, chyba że poproszą Państwo o informację zwrotną.

# The five edge-case categories with examples
edge_cases = {
    'Empty input':     ['[] empty array', '"" empty string', 'None / null'],
    'Single element':  ['[42]', 'single node tree', 'n=1'],
    'All same':        ['[3,3,3,3]', '"aaaa"', 'uniform grid'],
    'Extreme values':  ['[-10^9, 10^9]', 'INT_MAX + 1 overflow check', '0 as input'],
    'Already sorted':  ['ascending + descending', 'already optimal input'],
}
for category, examples in edge_cases.items():
    print(f'{category}:')
    for ex in examples:
        print(f'  - {ex}')
    print()

# Template for self-testing:
def test_my_solution(fn, test_cases):
    for inputs, expected in test_cases:
        result = fn(*inputs) if isinstance(inputs, tuple) else fn(inputs)
        status = 'PASS' if result == expected else 'FAIL'
        print(f'{status}: {inputs} => {result} (expected {expected})')

Obsługa pytań dodatkowych

Po rozwiązaniu zadania osoby rekrutujące zazwyczaj zadają pytania dodatkowe. Najczęstsze typy pytań to:

  • „Czy można rozwiązać to przy użyciu O(1) pamięci?” → Proszę poszukać modyfikacji w miejscu lub sztuczek matematycznych
  • „Co, jeśli n jest bardzo duże?” → Proszę omówić podejścia strumieniowe, stronicowanie lub próbkowanie
  • „Co, jeśli tablica jest już posortowana?” → Często istnieje prostszy algorytm
  • „Czy można to zrównoleglić?” → Proszę wskazać niezależne podproblemy i omówić MapReduce lub równoległość zadań

Pytania dodatkowe sprawdzają głębię wiedzy i elastyczność. Zamiast od razu zgadywać, proszę powiedzieć: „Proszę dać mi chwilę na zastanowienie”. Przemyślana pauza jest lepsza niż pewnie udzielona błędna odpowiedź.

# Follow-up answers for classic problems
follow_ups = [
    {
        'problem': 'Find duplicate in array 1..n (space O(n) solution uses set)',
        'follow_up': 'Can you do it in O(1) space without modifying input?',
        'answer': 'Floyd cycle detection: treat array as linked list (slow/fast pointer)',
    },
    {
        'problem': 'Reverse a string (space O(n) with new array)',
        'follow_up': 'Can you do it in-place?',
        'answer': 'Two pointers from both ends, swap until they meet: O(n) time O(1) space',
    },
    {
        'problem': 'Find max in array: O(n) single pass',
        'follow_up': 'What if the array is streamed one element at a time?',
        'answer': 'Same algorithm works! Running maximum handles infinite streams',
    },
    {
        'problem': 'Merge sorted arrays O(n+m)',
        'follow_up': 'What if you have K sorted arrays?',
        'answer': 'Use a min-heap of (value, array_idx, element_idx): O(n log k)',
    },
]
for fu in follow_ups:
    print(f'Problem: {fu["problem"]}')
    print(f'Follow-up: {fu["follow_up"]}')
    print(f'Answer: {fu["answer"]}\n')

Zadanie do przećwiczenia: grupowanie anagramów

Problem: Dany jest array napisów. Proszę pogrupować razem anagramy. Należy zwrócić listę grup.

Wzorzec: Mapa częstotliwości jako klucz. Dla każdego napisu należy posortować jego znaki (lub obliczyć krotkę częstotliwości znaków) i użyć jej jako kanonicznego klucza. Napisy należy grupować według tego klucza, korzystając z hash mapy list. Czas O(n × m log m), gdzie m oznacza maksymalną długość napisu, pamięć O(n × m). Zagnieżdżone pętle nie są potrzebne — wystarczy jedno przejście przez tablicę.

from collections import defaultdict

def group_anagrams(strs):
    # Method 1: sort each string as key
    groups = defaultdict(list)
    for s in strs:
        key = ''.join(sorted(s))   # canonical form
        groups[key].append(s)
    return list(groups.values())

def group_anagrams_v2(strs):
    # Method 2: character count tuple as key (avoids sorting)
    groups = defaultdict(list)
    for s in strs:
        count = [0] * 26
        for c in s:
            count[ord(c) - ord('a')] += 1
        key = tuple(count)   # immutable, hashable
        groups[key].append(s)
    return list(groups.values())

test = ['eat', 'tea', 'tan', 'ate', 'nat', 'bat']
result = [sorted(g) for g in group_anagrams(test)]
result.sort()
print('Groups:', result)
# [['ate','eat','tea'], ['bat'], ['nat','tan']]

print('V2:', [sorted(g) for g in sorted(group_anagrams_v2(test), key=len)])

Samoocena po rozmowie próbnej

Po każdej rozmowie próbnej proszę ocenić się w następujących obszarach:

  • Szybkość rozpoznawania wzorców: Czy udało się rozpoznać wzorzec w czasie <60 sekund?
  • Poprawność kodu: Czy pierwsze rozwiązanie przeszło wszystkie przypadki testowe?
  • Obsługa przypadków brzegowych: Czy przetestowano puste dane, pojedyncze elementy i wartości skrajne?
  • Komunikacja: Czy sposób rozumowania był wyjaśniany na bieżąco?
  • Świadomość złożoności: Czy określono złożoność czasową i pamięciową?
  • Radzenie sobie z trudnościami: Czy w przypadku utknięcia udało się płynnie zmienić podejście, czy nastąpiło zablokowanie?

Proszę przyznać sobie ocenę od 1 do 5 w każdym obszarze. Kolejny tydzień ćwiczeń proszę poświęcić obszarowi z najniższą oceną. Większość kandydatów musi poprawić rozpoznawanie wzorców albo komunikację — rzadko oba te obszary jednocześnie.

# Self-assessment scoring template
def self_assess(pattern_speed, code_correctness, edge_cases,
                communication, complexity, recovery):
    scores = {
        'Pattern recognition (< 60s)': pattern_speed,
        'Code correctness (all tests pass)': code_correctness,
        'Edge case handling': edge_cases,
        'Communication (thinking aloud)': communication,
        'Complexity stated correctly': complexity,
        'Recovery when stuck': recovery,
    }
    total = sum(scores.values())
    max_total = len(scores) * 5
    print('Self-Assessment Results:')
    print('-'*50)
    for dim, score in scores.items():
        bar = '#' * score + '-' * (5 - score)
        print(f'{dim:45s} [{bar}] {score}/5')
    print(f'\nTotal: {total}/{max_total} ({total/max_total*100:.0f}%)')
    weak = min(scores, key=scores.get)
    print(f'Focus area: {weak}')

self_assess(4, 3, 4, 3, 5, 2)  # example scores

Szybki test

Proszę sprawdzić swoje zrozumienie zagadnień z kursu Data Structures & Algorithms — Coding Interview Prep omówionych w tej lekcji.

Podsumowanie lekcji

W tej lekcji nauczyli się Państwo: podchodzić do zadań według ustalonego procesu — przeczytać treść, rozpoznać wzorzec w ciągu 60 sekund, określić złożoność, napisać kod, a następnie przetestować go według pięciu kategorii przypadków brzegowych, działające rozwiązanie brute-force jest lepsze od nieukończonego rozwiązania optymalnego, gdy kończy się czas oraz że samoocena po każdej próbnej sesji ćwiczeniowej w sześciu obszarach (szybkość, poprawność, przypadki brzegowe, komunikacja, złożoność, radzenie sobie z trudnościami) pozwala skupić rozwój na właściwych obszarach. W następnej części szczegółowo omówimy obsługę przypadków brzegowych oraz dobre praktyki komunikacji osoby ubiegającej się o pracę.

Często zadawane pytania

Czy lekcja „Próbna rozmowa techniczna na czas: problemy łatwe i średnie” jest bezpłatna?

Tak — pełny tekst „Próbna rozmowa techniczna na czas: problemy łatwe i średnie” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Coding Interview Prep, przejdź na CoddyKit PRO. Kurs Coding Interview Prep zawiera 4 lekcji w sumie.

Co nauczysz się w „Próbna rozmowa techniczna na czas: problemy łatwe i średnie”?

Rozwiązać trzy problemy w ciągu 45 minut, werbalizować tok rozumowania tak jak podczas prawdziwej rozmowy oraz przeanalizować później optymalne rozwiązania Ćwiczysz Coding Interview Prep z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Coding Interview Prep?

Nie wymagamy żadnego doświadczenia. Coding Interview Prep w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Próbna rozmowa techniczna na czas: problemy łatwe i średnie”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Coding Interview Prep?

Tak. Każda lekcja Coding Interview Prep zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Ściągawka rozpoznawania wzorców
  2. Próbna rozmowa techniczna na czas: problemy łatwe i średnie
  3. Przypadki brzegowe i komunikacja podczas rozmowy technicznej
  4. Omówienie trudnych problemów: Word Ladder II i Alien Dictionary
← Powrót do Coding Interview Prep