Lesson 3
Optimizing Array Segmentations Using TypeScript Maps
Introduction

Hello there! Welcome to this exciting TypeScript coding lesson, where we'll explore the performance efficiencies gained by utilizing Map types. Our focus will be on solving a problem involving arrays, where we aim to make an optimal choice to minimize the size of segments. Ready to dive in? Let's get started!

Task Statement

In this unit's task, we're tasked with creating a TypeScript function named minimalMaxBlock(). This function should accept an array of integers and compute an interesting property related to contiguous segments within the array.

Specifically, you'll need to select a particular integer, k, from the array. By choosing k, the function will remove all occurrences of k from the array, splitting it into several contiguous segments. The unique aspect of k is that it should be chosen such that the maximum length among these segments is minimized.

For example, with the list [1, 2, 2, 3, 1, 4, 4, 4, 1, 2, 5], removing all instances of 2 results in blocks [1], [3, 1, 4, 4, 4, 1], [5], with the longest containing six elements. However, if we remove all instances of 1, the remaining blocks are [2, 2, 3], [4, 4, 4], [2, 5], with the longest containing three elements. Therefore, the function should return 1, minimizing the maximal block length.

Brute Force Approach

An initial approach to this problem is a brute force method. We can test each possible value in the array by removing it and checking the resulting sub-array sizes. This method involves iterating through the array for each possible k.

TypeScript
1function minimalMaxBlockBruteforce(array: number[]): number { 2 let minMaxBlockSize: number = Number.MAX_VALUE; 3 let minNum: number = -1; 4 5 const uniqueElements: Set<number> = new Set(array); 6 7 uniqueElements.forEach((num: number) => { 8 const indices: number[] = []; 9 for (let i: number = 0; i < array.length; ++i) { 10 if (array[i] === num) { 11 indices.push(i); 12 } 13 } 14 indices.unshift(-1); 15 indices.push(array.length); 16 17 let maxBlockSize: number = 0; 18 for (let i: number = 1; i < indices.length; ++i) { 19 maxBlockSize = Math.max(maxBlockSize, indices[i] - indices[i - 1] - 1); 20 } 21 22 if (maxBlockSize < minMaxBlockSize) { 23 minMaxBlockSize = maxBlockSize; 24 minNum = num; 25 } 26 }); 27 28 return minNum; 29}

This method has a time complexity of O(n^2) due to two nested loops: an outer loop iterating through each potential k value, and an inner one through the array for each k.

Setup the Solution Approach

To solve the problem efficiently, we need to track each number's positions, the block size when removing each element, and store the maximum of these blocks.

Here, we won't need all positions, just the last occurrence, since we're interested in blocks between two adjacent elements, between the start of the array and the first occurrence, and between the last occurrence and the array's end. We'll keep track of the maximum block size for each element, updating as necessary.

Steps to Implement
  • Initialize two Maps: lastOccurrence to store the last positions of each number, and maxBlockSizes to map each number to its block's maximum size.

  • Traverse the array:

    • If a number is encountered for the first time, consider the block from the start to its position, storing the size in maxBlockSizes.
    • If the number has occurred before, calculate the block size it forms, updating maxBlockSizes if necessary.
    • Store the current index as the last occurrence of the number.
  • Calculate the "tail block" size (the block from the last occurrence of a number to the array's end) for each number and update maxBlockSizes accordingly.

  • Return the number with the minimal maximum block size.

Initialize the Maps

We'll initialize the Map structures with proper key and value types specified.

TypeScript
1function minimalMaxBlock(array: number[]): number { 2 const lastOccurrence: Map<number, number> = new Map(); 3 const maxBlockSizes: Map<number, number> = new Map();
Traverse the Array

We'll iterate over the array and apply logic with TypeScript types incorporated.

TypeScript
1 for (let i: number = 0; i < array.length; ++i) { 2 const num: number = array[i]; 3 if (!lastOccurrence.has(num)) { 4 maxBlockSizes.set(num, i); 5 } else { 6 const blockSize: number = i - lastOccurrence.get(num)! - 1; 7 maxBlockSizes.set(num, Math.max(maxBlockSizes.get(num)!, blockSize)); 8 } 9 lastOccurrence.set(num, i); 10 }
Handle Tail Blocks

Next, calculate and update tail block sizes with TypeScript annotations.

TypeScript
1 lastOccurrence.forEach((pos: number, num: number) => { 2 const blockSize: number = array.length - pos - 1; 3 maxBlockSizes.set(num, Math.max(maxBlockSizes.get(num)!, blockSize)); 4 });
Return the Optimal Result

Finally, determine the number with the smallest block size and return it, fully typed.

TypeScript
1 let minNum: number = -1; 2 let minBlockSize: number = Number.MAX_VALUE; 3 maxBlockSizes.forEach((blockSize: number, num: number) => { 4 if (blockSize < minBlockSize) { 5 minBlockSize = blockSize; 6 minNum = num; 7 } 8 }); 9 10 return minNum; 11}
Full Code with Example Usage

Here's the complete TypeScript code for our optimized minimalMaxBlock function, leveraging Map for efficient traversal.

TypeScript
1function minimalMaxBlock(array: number[]): number { 2 const lastOccurrence: Map<number, number> = new Map(); 3 const maxBlockSizes: Map<number, number> = new Map(); 4 5 for (let i: number = 0; i < array.length; ++i) { 6 const num: number = array[i]; 7 if (!lastOccurrence.has(num)) { 8 maxBlockSizes.set(num, i); 9 } else { 10 const blockSize: number = i - lastOccurrence.get(num)! - 1; 11 maxBlockSizes.set(num, Math.max(maxBlockSizes.get(num)!, blockSize)); 12 } 13 lastOccurrence.set(num, i); 14 } 15 16 lastOccurrence.forEach((pos: number, num: number) => { 17 const blockSize: number = array.length - pos - 1; 18 maxBlockSizes.set(num, Math.max(maxBlockSizes.get(num)!, blockSize)); 19 }); 20 21 let minNum: number = -1; 22 let minBlockSize: number = Number.MAX_VALUE; 23 maxBlockSizes.forEach((blockSize: number, num: number) => { 24 if (blockSize < minBlockSize) { 25 minBlockSize = blockSize; 26 minNum = num; 27 } 28 }); 29 30 return minNum; 31} 32 33// Example usage 34const exampleArray: number[] = [1, 2, 2, 3, 1, 4, 4, 4, 1, 2, 5]; 35const result: number = minimalMaxBlock(exampleArray); 36console.log(`The integer to remove for optimal segments is: ${result}`); 37// Expected output: The integer to remove for optimal segments is: 1
Lesson Summary

Well done! This unit tackled an interesting problem while exploring TypeScript's benefits — particularly its type system. By leveraging Map and array manipulation, we've enhanced performance and accuracy. Your task is to apply this knowledge to solve ever more complex challenges. Let's continue this journey and master the art of TypeScript programming!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.