ADS 2026 · KBTU · Lecture 1 · Lab 1

Complexity and Memory

The first class: how the course and the labs work, then how we measure the cost of an algorithm, four classic number algorithms — GCD, fast powers, the sieve and factorization — and the stack.

Length ~115 minBeats 14Live demos 7Practice Lab 1 · 10 problems
01

Lab contests and the defence

10 min demo: score calculator for the students

What to say

  • There are 12 lab contests, one a week from week 3. Each is worth its own number of points, listed in the syllabus.
  • A contest is submitted on ejudge, but it counts only after the defence: two weeks after the lab opens, a separate contest with 2 problems from the same list, solved on the spot.
  • Say plainly why the defence multiplies instead of adding: an accepted submission you cannot reproduce is worth nothing. Solving 5 problems honestly beats copying 10.
  • The two defence problems come from the whole list, not from what someone happened to solve — so leaving half the lab untouched is a gamble on both counts.
  • Let them play with the calculator for a minute — it answers "what do I get if…" faster than you can.

Lab contests

There are 12 lab contests across the semester, one per week starting from week 3, plus a TSIS assignment each week. Every contest carries its own point value — the syllabus lists them next to the week.

ContestWeekPoints
LabContest 133
LabContest 243
LabContest 353
LabContest 463
LabContest 573
LabContest 682
LabContest 7 … 12 — see the syllabus
Say it out loud: a lab contest is submitted on ejudge, but the points are awarded only after the defence, where you rewrite two of your solved problems on the spot. A green AC on its own is not the grade — the exact formula is on the next card.

The rule

LabContest 1 contains 10 problems, A to J, and is worth 3 points. Solve as many as you can on ejudge.

Two weeks after the lab opens a separate defence contest runs. It holds 2 problems taken from this same list of 10 — the same two for everyone — and you solve them there, on the spot, from scratch.

Your result on the defence contest, as a percentage, is multiplied by the percentage of the lab you solved.

Formula

score = L × Rlab × Rdef
SymbolMeaningLabContest 1
Lpoints this lab contest is worth (from the syllabus)3
Rlabshare of the lab you solved — solved ÷ totalS / 10
Rdefyour result on the 2-problem defence contest, as a share0 … 100 %

Rounded to two decimals. The same formula applies to every lab contest — only L and the number of problems change.

The defence multiplies, it does not add. Ten accepted problems with a zero on the defence contest give 3 × 1.00 × 0 = 0. And it cuts the other way too: acing the defence without having solved the lab gives 3 × 0 × 1.00 = 0. Both halves are required.

Demo · what will I get?

Points earned
3.00
3 × 1.00 × 1.00 = 3.00

Full marks — the whole lab solved and the defence contest passed in full.

LabDefencePointsWhat it means
10 / 10100 %3.00everything solved, defence clean
10 / 1070 %2.10whole lab solved, defence partly
8 / 1070 %1.6880 % of the lab × 70 % of the defence
10 / 1050 %1.50one defence problem out of two
5 / 10100 %1.50half the lab, defence clean
5 / 1070 %1.05
10 / 100 %0.00defence failed — the lab does not count
0 / 10100 %0.00lab not done — the defence does not count
02

Course Overview

3 min board

What the course consists of

  • Syllabus, covered topics, laboratory works
  • Midterm, endterm, final examinations
  • Ejudge.kz system, GitHub, defence rules
  • Labs — practice lessons to help solving labs
03

Course Topics by Lecture

4 min board

What to say

  • Walk the 13 lectures: the course builds from simple structures up to graphs, and what we do today comes back at the end.

Thirteen lectures

LectureTitleTopics
1Complexity and MemoryGCD, Binary Exponentiation, Sieve of Eratosthenes, Prime factorization, Stack, Queue, Deque
2Data StructuresStack, Queue, Deque, Linked lists, Doubly-Linked lists
3Search AlgorithmsBinary search
4Tree StructuresBinary search tree
5Priority StructuresPriority queues, Heap
6Sorting Algorithms IHeap sort, Quick Sort
7Sorting Algorithms IIMerge sort, Midterm
8Hash TablesHash tables, Rabin-Karp algorithm based on hash calculation
9String AlgorithmsKnuth-Morris-Pratt algorithm
10Graph Representation and BFSAdjacency list and matrix, Edge list, BFS
11Graph Traversal and SortingDFS, Topological Sort
12Spanning Tree AlgorithmsKruskal algorithm, Prim algorithm
13Shortest Path AlgorithmsDijkstra, Floyd-Warshall, Bellman-Ford
04

Greatest Common Divisor (GCD)

12 min demo: side by side code

What to say

  • Start with the simple solution and show why we need something better: check every number from min(a, b) down to 1.
  • For gcd(12, 18) it is fine — it stops at 6 after 7 checks. For gcd(1000000007, 1000000009) it needs about a billion checks. Run the demo and read the number off the screen.
  • Then the Euclidean algorithm: gcd(a, b) = gcd(b, a mod b), and gcd(a, 0) = a. Do a few steps by hand on the board.
  • Three implementations: the naive loop, the recursive Euclid, and an iterative one that keeps taking the remainder of the larger by the smaller.

The problem and the naive approach

Problem: find the largest positive integer that divides both numbers a and b.

Naive approach

  • Check all numbers from min(a, b) down to 1
  • Time complexity: O(min(a, b))
  • Inefficient for large numbers

Example

  • For small numbers like GCD(12, 18) it works fine: at most 12 checks — it actually stops at 6 after 7
  • For large primes like GCD(1000000007, 1000000009) it needs about 1 billion checks
  • With large numbers this approach takes an extremely long time

The Euclidean algorithm

GCD(a, b) = GCD(b, a mod b) if b ≠ 0
GCD(a, 0) = a

On the board:

gcd(12, 18) = gcd(18, 12) = gcd(12, 6) = gcd(6, 0) = 6
gcd(48, 180) = gcd(180, 48) = gcd(48, 36) = gcd(36, 12) = gcd(12, 0) = 12

Implementation

int gcd(int a, int b) {
    for (int i = min(a, b); i >= 1; --i) {
        if (a % i == 0 && b % i == 0) {
            return i;
        }
    }
    return 1;
}
int gcd2(int a, int b) {
    if (b == 0) {
        return a;
    }
    return gcd2(b, a % b);
}
// Iterative version
int gcd1(int a, int b) {
    while (a > 0 && b > 0) {
        if (a > b)
            a = a % b;
        else
            b = b % a;        
    }

    // if (a > 0)
    //     return b;
    // return a;
    return a + b;
}
GCD — the full program41 lines
#include <iostream>

using namespace std;

int gcd(int a, int b) {
    for (int i = min(a, b); i >= 1; --i) {
        if (a % i == 0 && b % i == 0) {
            return i;
        }
    }
    return 1;
}

int gcd2(int a, int b) {
    if (b == 0) {
        return a;
    }
    return gcd2(b, a % b);
}

// Iterative version
int gcd1(int a, int b) {
    while (a > 0 && b > 0) {
        if (a > b)
            a = a % b;
        else
            b = b % a;        
    }

    // if (a > 0)
    //     return b;
    // return a;
    return a + b;
}

int main() {
    int a, b;
    cin >> a >> b;
    cout << gcd2(a, b) << endl;
    return 0;
}
input
12 18
output
6

Demo · how many steps each one takes

Euclid, iterations
—
Brute force, checks
—
GCD
—
stepaba mod b

Another version · with long long

The same three functions on long long. That is what problem A needs: a and b go up to 10¹⁸, which does not fit into int.

the full program — version 238 lines
#include <iostream>
#define ll long long

using namespace std;

ll gcd1(ll a, ll b) {
    for (ll i = min(a, b); i >= 1; --i) {
        if (a % i == 0 && b % i == 0) {
            return i;
        }
    }
    return 1;
}

ll gcd2(ll a, ll b) {
    if (b == 0) {
        return a;
    }
    return gcd2(b, a % b);
}

ll gcd3(ll a, ll b) {
    while (a > 0 && b > 0) {
        if (a > b) {
            a = a % b;
        } else {
            b = b % a;
        }
    }
    return a + b;
}

int main() {
    ll a, b;
    cin >> a >> b;
    cout << gcd1(a, b) << endl;
    return 0;
}
input
12 18
output
6
05

Binary Exponentiation (Binpow)

12 min demo: step table code

What to say

  • Naively an costs n multiplications. For n = 10¹⁸ that is more than thirty years.
  • If n is even, an = (an/2)²; if n is odd, an = a · an−1. Every two steps halve the exponent — O(log n).
  • Do 210 by hand: 4 real multiplications instead of 10.
  • Then the version with a modulus — that is problem B — and its two traps.

The problem and the idea

Problem: calculate an efficiently.

Naive approach

  • Multiply a by itself n times
  • Time complexity: O(n)
  • Inefficient for large exponents

Binary exponentiation

  • Based on the property an = (an/2)2 if n is even
  • If n is odd: an = a × an−1
  • Time complexity: O(log n)

Algorithm

  1. If n = 0, return 1
  2. If n is even: result = (an/2)2
  3. If n is odd: result = a × an−1
  4. Use recursion or iteration

Example · 210

210 = (25)2 = (2 × 24)2 = (2 × (22)2)2 = (2 × 42)2 = (2 × 16)2 = 322 = 1024

What the recursive code below actually multiplies, in order:

2^1 = 2 × 1 = 2
2^2 = 2 × 2 = 4
2^4 = 4 × 4 = 16
2^5 = 2 × 16 = 32
2^10 = 32 × 32 = 1024

4 real multiplications instead of 10 — the first line, 2 × 1, is the base case and costs nothing.

Implementation

// Recursive version
long long binpow(long long a, long long n) {
    if (n == 0)
        return 1;
    if (n % 2 == 0) {
        long long half = binpow(a, n / 2);
        return half * half;
    } else {
        return a * binpow(a, n - 1);
    }
}
// Iterative version
long long binpow_iter(long long a, long long n) {
    long long result = 1;
    while (n > 0) {
        if (n % 2 == 1) {
            result *= a;
        }
        a *= a;
        n /= 2;
    }
    return result;
}
// Example usage
int main() {
    long long base, exp;
    cin >> base >> exp;
    cout << "Result: " << binpow(base, exp) << endl;
    cout << "Result (iterative): " << binpow_iter(base, exp) << endl;
    return 0;
}
input
2 10
output
Result: 1024
Result (iterative): 1024

Demo · step by step

Multiplications, binpow
—
Multiplications, naive
—
Answer
—
n in binary: 1010
nbitresulta

Another version · modulo m, and the naive loop

For problem B the answer is an mod m. The naive loop for comparison:

ll pow_a_b(ll a, ll b) {
    if (b == 0) 
        return 1;
    ll result = 1;
    for (ll i = 0; i < b; ++i) 
        result *= a;
    return result;
}

and binary exponentiation modulo m:

ll bin_pow(ll a, ll b, ll m) {
    if (b == 0) 
        return 1 % m;   // fixed: for m = 1 the answer is 0, not 1
    if (b % 2 == 0) {
        ll half = bin_pow(a, b / 2, m);
        return (half * half) % m;
    } else {
        return (a % m * bin_pow(a, b - 1, m)) % m;   // fixed: a up to 10^18 overflows without % m
    }
}
Two traps, both fixed above. For n = 0, m = 1 the answer is 0, so the base case returns 1 % m, not 1. And a goes up to 10¹⁸: multiplying it by a residue overflows long long, so it is reduced with a % m first.
the full program — version 230 lines
#include <iostream>
#define ll long long
using namespace std;

ll bin_pow(ll a, ll b, ll m) {
    if (b == 0) 
        return 1 % m;   // fixed: for m = 1 the answer is 0, not 1
    if (b % 2 == 0) {
        ll half = bin_pow(a, b / 2, m);
        return (half * half) % m;
    } else {
        return (a % m * bin_pow(a, b - 1, m)) % m;   // fixed: a up to 10^18 overflows without % m
    }
}

ll pow_a_b(ll a, ll b) {
    if (b == 0) 
        return 1;
    ll result = 1;
    for (ll i = 0; i < b; ++i) 
        result *= a;
    return result;
}

int main() {
    ll a, b, m;
    cin >> a >> b >> m;
    cout << bin_pow(a, b, m) << endl;
    return 0;
}
input
2 10 1000000000
output
1024
06

Prime Numbers — Sieve of Eratosthenes

12 min demo: animation code

What to say

  • The task is to find all primes up to n. Testing each number separately is O(n√n); the sieve does it in O(n log log n).
  • Start the animation and narrate it: the first number that is not crossed out is prime — cross out all its multiples.
  • Stop on why crossing starts at i²: every smaller multiple was already crossed by a smaller prime.

The problem and the naive approach

Problem: find all prime numbers up to n efficiently.

Naive approach

  • Check each number for primality individually
  • Time complexity: O(n√n)

Example · primes up to 30

Start with [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30]
Cross out multiples of 2: [2,3,5,7,9,11,13,15,17,19,21,23,25,27,29]
Cross out multiples of 3: [2,3,5,7,11,13,17,19,23,25,29]
Cross out multiples of 5: [2,3,5,7,11,13,17,19,23,29]
6² = 36 > 30 — stop, everything left is prime

Result: [2,3,5,7,11,13,17,19,23,29]

Complexity and the algorithm

  • Time complexity: O(n log log n) — much better than the naive approach
  • Space complexity: O(n)
  1. Create a boolean array is_prime[0..n], initialize all as true
  2. Set is_prime[0] = is_prime[1] = false
  3. For each number i from 2 to √n: if is_prime[i] is true, mark all multiples of i, starting from i², as false
  4. The remaining true values are primes

Demo · the sieve running

Current prime p
—
Primes found
0
Cross-outs
0
Checks if done one by one
0

■ current prime p · ■ being crossed out · ■ prime · ■ composite

Another version · trial division and the sieve side by side

Testing one number by dividing up to √n:

bool is_prime(int n) {
    if (n <= 1) return false;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) return false;
    }
    return true;
}

and the sieve — here 0 means "prime", 1 means "crossed out":

vector<int> primes(int n) {
    vector<int> is_prime(n + 1, 0);
    for (int i = 2; i * i <= n; i++) {
        if (is_prime[i] == 0) {
            for (int j = i * i; j <= n; j += i) {
                is_prime[j] = 1;
            }
        }
    }
    return is_prime;
}
the full program — version 242 lines
#include <iostream>
#include <vector>

using namespace std;

bool is_prime(int n) {
    if (n <= 1) return false;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) return false;
    }
    return true;
}

vector<int> primes(int n) {
    vector<int> is_prime(n + 1, 0);
    for (int i = 2; i * i <= n; i++) {
        if (is_prime[i] == 0) {
            for (int j = i * i; j <= n; j += i) {
                is_prime[j] = 1;
            }
        }
    }
    return is_prime;
}

int main() {
    int n;
    cin >> n;
    // for (int i = 1; i <= n; i++) {
    //     if (is_prime(i)) {
    //         cout << i << " ";
    //     }
    // }
    vector<int> p = primes(n);
    for (int i = 2; i <= n; i++) {
        if (p[i] == 0) {
            cout << i << " ";
        }
    }
    cout << endl;
    return 0;
}
input
30
output
2 3 5 7 11 13 17 19 23 29

Another version · the sieve returns the list of primes

the full program — version 331 lines
#include <iostream>
#include <vector>

using namespace std;

vector<int> prime_numbers(int n) {
    int a[n + 1];
    for (int i = 0; i <= n; i++)
        a[i] = 0;
    for (int i = 2; i * i <= n; i++) {
        if (a[i] == 0) {
            for (int j = i * i; j <= n; j+=i)
                a[j] = 1;
        }
    }
    vector<int> result;
    for (int i = 2; i <=n; i++)
        if (a[i] == 0)
            result.push_back(i);
    return result;

}

int main() {
    int n;
    cin >> n;
    vector<int> primes = prime_numbers(n);
    for (int i = 0; i < primes.size(); i++)
        cout << primes[i] << " ";
    return 0;
}
input
30
output
2 3 5 7 11 13 17 19 23 29
07

Prime Factorization

10 min demo: trial division code

What to say

  • Prime factorization breaks a number into a product of primes.
  • Dividing up to √n is enough. After the loop the leftover may be greater than 1 — and that leftover is a prime factor too. Show it in the demo on 999999999989.
  • The faster version divides only by primes, precomputed with the sieve.

Examples

60 = 2 × 2 × 3 × 5
60 ÷ 2 = 30 → ÷ 2 = 15 → ÷ 3 = 5 → 5 is prime

210 = 2 × 3 × 5 × 7
210 ÷ 2 = 105 → ÷ 3 = 35 → ÷ 5 = 7 → 7 is prime

Basic implementation

#include <iostream>
using namespace std;

void primeFactorization(int n) {
    for (int i = 2; i * i <= n; i++) {
        while (n % i == 0) {
            cout << i << " ";
            n /= i;
        }
    }
    if (n > 1) cout << n; // remaining prime
    cout << endl;
}

int main() {
    int num;
    cout << "Enter a number: ";
    cin >> num;
    cout << "Prime factors: ";
    primeFactorization(num);
    return 0;
}
input
60
output
Enter a number: Prime factors: 2 2 3 5

Worst case: O(√n) — trial division.

Demo · dividing step by step

Factorization
—
Iterations up to √n
—
Iterations if up to n
—
divisorn leftfactors so far

Faster · sieve + precomputed primes

// Generate primes up to limit using Sieve of Eratosthenes
vector<int> sieve(int limit) {
    vector<bool> isPrime(limit + 1, true);
    vector<int> primes;

    isPrime[0] = isPrime[1] = false;

    for (int i = 2; i * i <= limit; i++) {
        if (isPrime[i]) {
            for (int j = i * i; j <= limit; j += i) {
                isPrime[j] = false;
            }
        }
    }

    for (int i = 2; i <= limit; i++) {
        if (isPrime[i]) primes.push_back(i);
    }
    return primes;
}
// Prime factorization using precomputed primes
void primeFactorization(int n, const vector<int>& primes) {
    int temp = n;
    for (int p : primes) {
        if (p * p > n) break; // stop when p > sqrt(n)
        while (n % p == 0) {
            cout << p << " ";
            n /= p;
        }
    }
    if (n > 1) cout << n; // remaining prime factor
    cout << endl;
}
the full program54 lines
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

// Generate primes up to limit using Sieve of Eratosthenes
vector<int> sieve(int limit) {
    vector<bool> isPrime(limit + 1, true);
    vector<int> primes;

    isPrime[0] = isPrime[1] = false;

    for (int i = 2; i * i <= limit; i++) {
        if (isPrime[i]) {
            for (int j = i * i; j <= limit; j += i) {
                isPrime[j] = false;
            }
        }
    }

    for (int i = 2; i <= limit; i++) {
        if (isPrime[i]) primes.push_back(i);
    }
    return primes;
}

// Prime factorization using precomputed primes
void primeFactorization(int n, const vector<int>& primes) {
    int temp = n;
    for (int p : primes) {
        if (p * p > n) break; // stop when p > sqrt(n)
        while (n % p == 0) {
            cout << p << " ";
            n /= p;
        }
    }
    if (n > 1) cout << n; // remaining prime factor
    cout << endl;
}

int main() {
    int num;
    cout << "Enter a number: ";
    cin >> num;

    // Precompute primes up to sqrt(num)
    int limit = (int)(sqrt(num)) + 1;
    vector<int> primes = sieve(limit);

    cout << "Prime factors of " << num << ": ";
    primeFactorization(num, primes);

    return 0;
}
input
210
output
Enter a number: Prime factors of 210: 2 3 5 7

Efficiency

  • Sieve: O(n log log n), once
  • Factorization: O(π(√n)), where π(x) is the number of primes ≤ x — much fewer than √n divisions
08

Time Complexity and Memory

12 min demo: growth calculator

What to say

  • Sorting 10 numbers finishes instantly with any algorithm. Sorting 10 million takes hours with a slow one and seconds with a fast one.
  • So we do not measure seconds — we measure how fast the cost grows as n grows. Drag the slider and let the room watch n² fall out of the time limit.
  • Two things we drop: constant factors and less significant terms.
  • A judge does roughly 10⁸–10⁹ simple operations per second — from the n in the statement you can pick the complexity you are allowed.

Why complexity matters

  • Sorting 10 numbers → both good and bad algorithms finish instantly.
  • Sorting 10 million numbers → slow algorithms take hours, fast ones finish in seconds.
  • We use complexity analysis to predict how runtime and memory grow as n increases.

Big-O describes the upper bound on the growth rate — how fast runtime or memory usage can grow in the worst case.

Key principles

  • Ignore constant factors — 2n or 5n, it is still O(n)
  • Ignore less significant terms — n² + n → O(n²), because n² dominates for large n

Demo · how long an algorithm runs for a given n

ComplexityExampleOperationsTime at 10⁹ ops/s

Green fits inside one second, red does not. This is exactly the estimate to make before choosing an approach.

Common time complexities

ComplexityNameExample
O(1)ConstantAccess an array element
O(log n)LogarithmicBinary search
O(n)LinearLoop over an array
O(n log n)LinearithmicMerge sort, quicksort on average
O(n²)QuadraticNested loops
O(2ⁿ)ExponentialNaive Fibonacci recursion
O(n!)FactorialTraveling Salesman brute force

Example of O(2ⁿ) — very inefficient

int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2); // Very slow for large n
}
09

Space (Memory) Complexity

3 min board

Extra memory beyond the input

  • O(1) → constant extra space (variables, counters)
  • O(n) → memory proportional to n (arrays, hash maps)
  • O(n²) → large 2D matrices, graph adjacency matrices
10

Practical Takeaways

3 min board

Time, space, and the other bounds

  • Time complexity tells you how fast runtime grows.
  • Space complexity tells you how much extra memory grows.
  • Use Big-O for worst-case analysis, but know there is also Big-Θ (Theta) — a tight bound, the exact growth rate — and Big-Ω (Omega) — the best-case bound.

For competitive programming and large-scale apps, aim for

  • Sorting: O(n log n)
  • Searching: O(log n) or better
  • Graphs: BFS/DFS O(V + E)
  • Precomputation (like a sieve) can trade memory for speed
11

Stack — LIFO (Last In, First Out)

15 min demo: playground code

What to say

  • Think of a stack of plates: you can only add to or remove from the top. Every operation is O(1).
  • Use the playground to show the stack, then switch it to queue and deque — the same idea with other ends open.
  • pop() and top() on an empty stack are undefined behaviour, not an exception. Check empty() first.
  • Then the same stack written by hand on linked nodes — the preview of lecture 2.

Core operations

  • push(item) — add an item to the top — O(1)
  • pop() — remove the top item — O(1)
  • top() / peek() — view the top item without removing it — O(1)
  • empty() — check if the stack is empty — O(1)
  • size() — get the number of elements — O(1)

C++ implementation using the STL

// C++ built-in stack
#include <iostream>
#include <stack>
using namespace std;

int main() {
   stack<int> st;
   st.push(10);
   st.push(20);
   st.push(15);
   st.push(1);

   cout << st.top() << endl; // 1
   st.pop();
   st.pop();
   cout << st.top() << endl; // 20
   cout << st.size() << endl;
   cout << st.empty();
   return 0;
}
output
1
20
2
0

Demo · playground

The stack is empty. Press push to add an element.
StructureRuleInsert atRemove fromSTL
StackLIFOtoptopstd::stack
QueueFIFObackfrontstd::queue
Dequeboth endsfront and backfront and backstd::deque

Another version · Stack and Queue on linked nodes

No STL: each element is a Node with a pointer to the next one. The stack pushes and pops at the head; the queue keeps both front and rear.

the full program — version 2134 lines
#include <iostream>
#include <stack>

using namespace std;

class Node {
    public:
    int data;
    Node* next;
    Node(int val) {
        data = val;
        next = nullptr;
    }
};

class Stack {

    public:
    Node* top;
    int sz;
    Stack() {
        top = NULL;
        sz = 0;
    }

    void push(int val) {
        Node* newNode = new Node(val);
        newNode->next = top;
        top = newNode;
        sz++;
    }

    void pop() {
        if (top == NULL) 
            return;
        Node* temp = top;
        top = top->next;
        delete temp;
        sz--;
    }

    int peek() {
        if (top == NULL) 
            return -1; // or throw an exception
        return top->data;
    }

    int size() {
        return sz;
    }

    int empty() {
        return sz == 0;
    }
};

class Queue {
    public:

    Node* front;
    Node* rear;
    Queue() {
        front = NULL;
        rear = NULL;
    }

    void push(int val) {
        Node* newNode = new Node(val);
        if (rear == NULL) {
            front = rear = newNode;
            return;
        }
        rear->next = newNode;
        rear = newNode;
    }
    void pop() {
        if (front == NULL) 
            return;
        Node* temp = front;
        front = front->next;
        if (front == NULL) 
            rear = NULL;
        delete temp;
    }
    int peek() {
        if (front == NULL) 
            return -1; // or throw an exception
        return front->data;
    }
    int empty() {   
        return front == NULL;
    }
    int size() {
        int count = 0;
        Node* current = front;
        while (current != NULL) {
            count++;
            current = current->next;
        }
        return count;
    }

};

int main() {
    // stack<int> st;
    // st.push(10);
    // st.push(20);
    // st.push(30);
    // st.push(40);
    // cout << st.top() << endl;
    // st.pop();
    // cout << st.top() << endl;
    // cout << st.size() << endl;
    // cout << st.empty() << endl; 
    // Node* node1 = new Node(10);
    // Node* node2 = new Node(20);
    // Node* node3 = new Node(30);
    // node1->next = node2;
    // node2->next = node3;
    // cout << node1->data << " " << node2->data << " " << node3->data << endl;
    // cout << node1->next->next->data << endl;
    Stack *st = new Stack();
    st->push(10);
    st->push(20);   
    st->push(30);   
    st->push(40);   
    cout << st->peek() << endl;
    st->pop();
    cout << st->peek() << endl;
    cout << st->size() << endl;
    cout << st->empty() << endl;
    return 0;
}
output
40
30
3
0
12

Lab 1 — the problems

8 min ADS-Lab-01

What to say

  • The problems follow the order of the lecture: A–B are GCD and binpow, C–E are primes, F–H the stack, I–J the queue and the deque.
  • H is the hard one — a monotonic stack. Say plainly that it is an application of the stack.
  • Limits: 1 second and 256 MB almost everywhere.

Problem to topic

A
Greatest Common Divisorkey
GCD — Euclid on long long, a, b ≤ 10¹⁸
B
Binary Exponentiationkey
Binary exponentiation aⁿ mod m, n ≤ 10¹⁸ — mind 1 % m and a % m
C
Prime or Not
Primality by trial division up to √a
D
Prime Numbers
The n-th prime — sieve, the answer is at most 7919
E
Prime Factorizationkey
Factorize n ≤ 10¹² — do not lose the leftover prime
F
Equal Strings
Stack — backspace characters
G
Balanced Sequence of Letters
Stack — collapsing equal neighbouring pairs
H
Nugman and Stack
Monotonic stack — the nearest strictly smaller to the left
I
Royal Flush
Queue — simulating the card trick
J
Boris vs Nursik
Deque — simulating the card game