This tutorial is a part of the Data Structures and Algorithms class:
- A typical problem solved by Greedy algorithm
- Characteristics of problems can be solved by Greedy
- Summary
A typical problem solved by Greedy algorithm
- Make the Greediest choice in current step and hope for the most optimal solution after all steps.
- Also called: local optimal hopefully will also be global optimal. Sometimes it is, most times it isn't.
- Greedy is too simple. It can only solve very simple problems. But problems are usually not simple.
- Then why learn Greedy? Popularity, historical reason, before better algo (DP for ex) comes along.
lecture-21/coin-change.js
// return the minimum number of coins to sum up to a given sum
const greedy = (coins, sum) => {
coins.sort((a, b) => b - a) // O(nlogn)
console.log('coins:', coins)
console.log('sum:', sum)
const result = {}
let sumRemain = sum
for(let i = 0; i < coins.length; i++) { // O(n)
const numCoin = Math.floor(sumRemain/coins[i])
if(numCoin > 0) {
result[coins[i] + ' coin(s)'] = numCoin
sumRemain = sumRemain - numCoin * coins[i]
}
}
result.sumRemain = sumRemain
return result
}
console.log(JSON.stringify(greedy([1, 2, 5], 21), null, 4));
console.log('========================')
console.log(JSON.stringify(greedy([2, 5], 21), null, 4));
// Time complexity: O(nlogn), with n is number of coin types
$ node coin-change.js
coins: [ 5, 2, 1 ]
sum: 21
{
"5 coin(s)": 4,
"1 coin(s)": 1,
"sumRemain": 0
}
========================
coins: [ 5, 2 ]
sum: 21
{
"5 coin(s)": 4,
"sumRemain": 1
}
Characteristics of problems can be solved by Greedy
- Not recommended to use in coding interview. Can use in real life application if no choice left.
Problem | Input characteristics | Algorithm | Time Complexity |
---|---|---|---|
Maximization, Minimization | - usually array, string or graph | Greedy choice, no guarantee for most optimal solution |
varies |
lecture-21/max-sum-path.js
// Find max sum path from root to leaf
// structure of a node
class Node {
constructor(data) {
this.data = data
this.left = null
this.right = null
}
}
// Binary Tree
class BinaryTree {
constructor(data) {
this.root = data ? new Node(data) : null
}
}
// create new binary tree
const binaryTree = new BinaryTree()
binaryTree.root = new Node(5) // add root node, 5
// add level 1 nodes: 3, 2
binaryTree.root.left = new Node(3)
binaryTree.root.right = new Node(2)
// add level 2 nodes: 7, 4
binaryTree.root.left.left = new Node(7)
binaryTree.root.right.right = new Node(4)
console.log(JSON.stringify(binaryTree, null, 4))
const sum1 = { max: 0 }
const greedySumPath = (node, sum) => {
if(!node) return sum
sum.max = sum.max + node.data
if(node.left?.data >= node.right?.data || node.left?.data && !node.right?.data) {
console.log('next node on left:', node.left.data)
greedySumPath(node.left, sum)
}
if(node.left?.data < node.right?.data || node.right?.data && !node.left?.data) {
console.log('next node on right:', node.right.data)
greedySumPath(node.right, sum)
}
return sum
}
// Time complexity: worst case O(n), n is total number of nodes
console.log(greedySumPath(binaryTree.root, sum1))
console.log('========================')
// create new binary tree
const binaryTree2 = new BinaryTree()
binaryTree2.root = new Node(5)
binaryTree2.root.left = new Node(3)
binaryTree2.root.right = new Node(2)
binaryTree2.root.left.left = new Node(1)
binaryTree2.root.right.right = new Node(8)
console.log(JSON.stringify(binaryTree2, null, 4))
const sum2 = { max: 0 }
console.log(greedySumPath(binaryTree2.root, sum2))
$ node max-sum-path.js
{
"root": {
"data": 5,
"left": {
"data": 3,
"left": {
"data": 7,
"left": null,
"right": null
},
"right": null
},
"right": {
"data": 2,
"left": null,
"right": {
"data": 4,
"left": null,
"right": null
}
}
}
}
next node on left: 3
next node on left: 7
{ max: 15 }
========================
{
"root": {
"data": 5,
"left": {
"data": 3,
"left": {
"data": 1,
"left": null,
"right": null
},
"right": null
},
"right": {
"data": 2,
"left": null,
"right": {
"data": 8,
"left": null,
"right": null
}
}
}
}
next node on left: 3
next node on left: 1
{ max: 9 }
Summary
Prob. class | P. sub-class | Input characteristics | Algorithm | Time Complexity |
---|---|---|---|---|
(almost) ANY problem class | array, matrix, ... | Brute Force | O(n) & up |
|
1. Maximization 2. Minimization |
array, string or graph, ... | Greedy choice, no guarantee for most optimal solution |
varies |
|
3. Find all options | array, matrix, ... | Brute Force | O(n) & up |
|
4. Find most optimal solution | ||||
5. Find a truth | search for a number | - sorted array or balanced BST |
Binary Search | O(logn) |
Real life interview questions
All problems below can be solved without Greedy algorithm. You can solve them by logical thinking and are not forced to use Greedy. However, thinking about a Greedy approach (if possible) is encouraged afterward.
- Greedy is a simple algorithm. Why is it simple and how does Greedy solve problems?
- What kind of problems can be typically solved by Greedy?
- (Easy) Leetcode #561: Array Partition
- (Easy) Leetcode #2160: Minimum Sum of Four Digit Number After Splitting Digits
- (Easy) Leetcode #2099: Find Subsequence of Length K With the Largest Sum
- (Easy) Leetcode #1323: Maximum 69 Number
- (Easy) Leetcode #1827: Minimum Operations to Make the Array Increasing
- (Medium) Leetcode #763: Partition Labels
- Solve all problems above, including the two from the tutorial with recursion/loop.
- More on Leetcode #greedy. Progress check: you should be fairly comfortable doing any of problems in related subject after finishing all problems above.