├── .gitignore ├── .rspec ├── 01-1-binary_gap.rb ├── 02-1-cyclic_rotation.js ├── 02-2-odd_occurrences_in_array.rb ├── 03-1-tape_equilibrium.rb ├── 03-2-frog_jump.rb ├── 03-3-perm_missing_elem.rb ├── 04-1-perm_check.rb ├── 04-2-frog_river_one.rb ├── 04-3-missing_integer.js ├── 04-3-missing_integer.rb ├── 04-4-max_counters.rb ├── 05-1-count_div.rb ├── 05-2-passing_cars.rb ├── 05-3-genomic_range_query.rb ├── 05-4-min_avg_2_slice.rb ├── 06-1-distinct.rb ├── 06-2-triangle.rb ├── 06-3-max_product_of_3.rb ├── 06-4-disc_intersections.rb ├── 07-1-brackets_spec.rb ├── 07-2-fish.rb ├── 07-3-stonewall.rb ├── 07-4-nesting.rb ├── 08-1-dominator.rb ├── 08-2-equi_leader.rb ├── 09-1-max_slice_sum.rb ├── 09-2-max_double_slice_sum.rb ├── 09-3-max_profit.rb ├── 10-1-count_factors.rb ├── 10-2-min_perimeter_rectangle.rb ├── 10-3-peaks.rb ├── 10-4-flags.rb ├── 11-1-count_semiprimes.rb ├── 11-2-count_not_divisible.rb ├── 12-1-chocolates_by_numbers.rb ├── 12-2-common_prime_divisors.rb ├── 13-1-ladder.rb ├── 13-2-frog.rb ├── 14-1-nailing_planks.rb ├── 14-2-min_max_division.rb ├── 15-1-abs_distinct.rb ├── 15-2-count_triangles.rb ├── 15-3-count_distinct_slices.rb ├── 16-1-max_nonoverlapping_segments.rb ├── 16-2-tie_ropes.rb ├── 17-1-number_solitaire.rb ├── 17-2-min_abs_sum_spec.rb ├── 99-1-sql_sum.sql ├── 99-2-str_symmetry_point.rb ├── 99-3-tree_height.rb ├── Gemfile ├── Gemfile.lock ├── README.md ├── autocomplete.rb ├── binary_search.rb ├── caterpillar_method.rb ├── counting_sort.rb ├── dynamic_coin_change.rb ├── equi.rb ├── fibonacci.rb ├── gcd.rb ├── google_interview.rb ├── greedy_coins_and_canoes.rb ├── grocery_store.rb ├── leader.rb ├── lessons ├── 01-Iterations.pdf ├── 02-Arrays.pdf ├── 03-TimeComplexity.pdf ├── 04-CountingElements.pdf ├── 05-PrefixSums.pdf ├── 06-Sorting.pdf ├── 07-Stacks and Queues.pdf ├── 08-Leader.pdf ├── 09-MaxSlice.pdf ├── 10-PrimeNumbers.pdf ├── 11-Sieve.pdf ├── 12-Gcd.pdf ├── 13-Fibonacci.pdf ├── 14-BinarySearch.pdf ├── 15-CaterpillarMethod.pdf ├── 16-GreedyAlgorithms.pdf └── 17-DynamicProgramming.pdf ├── max_slice.rb ├── merge_sort.rb ├── permutation.rb ├── prefix_sums.rb ├── prime_numbers.rb ├── sieve_of_eratosthenes.rb ├── solution-flags.pdf └── subset_sum.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /real_problems 2 | practice 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /01-1-binary_gap.rb: -------------------------------------------------------------------------------- 1 | def binary(m) 2 | bin = [] 3 | begin 4 | bin.unshift(m % 2) 5 | m /= 2 6 | end until m == 0 7 | bin.join 8 | end 9 | 10 | def solution(n) 11 | bin = binary(n) # or n.to_s(2) does direct binary conversion 12 | zeroes = bin.split('1') 13 | return 0 if zeroes.empty? 14 | zeroes.pop unless bin[-1] == '1' 15 | zeroes.max.length 16 | end 17 | 18 | # Convert 25 to binary = ‘11001’ 19 | 20 | # 25 / 2 = 12 21 | # 25 % 2 = 1 * 22 | 23 | # 12 / 2 = 6 24 | # 12 % 2 = 0 * 25 | 26 | # 6 / 2 = 3 27 | # 6 % 2 = 0 * 28 | 29 | # 3 / 2 = 1 30 | # 3 % 2 = 1 * 31 | 32 | # 1 / 2 = 0 33 | # 1 % 2 = 1 * 34 | 35 | # Binary number = all mod answers combined backwards! -------------------------------------------------------------------------------- /02-1-cyclic_rotation.js: -------------------------------------------------------------------------------- 1 | function whereItGoes(start, K, arrayLength) { 2 | var K = K % arrayLength; 3 | if (start + K < arrayLength) 4 | return start + K; 5 | else 6 | return start + K - arrayLength; 7 | } 8 | 9 | function solution(A, K) { 10 | var len = A.length; 11 | if (len === 0 || len === 1) return A; 12 | var destination = whereItGoes(0, K, len); 13 | var slicenum = len - destination; 14 | A = A.slice(slicenum).concat(A.slice(0, slicenum)) 15 | return A; 16 | } 17 | 18 | // Ruby answer: too simple! 19 | // def solution(a, k) 20 | // a.rotate(-k) 21 | // end -------------------------------------------------------------------------------- /02-2-odd_occurrences_in_array.rb: -------------------------------------------------------------------------------- 1 | def solution(a) 2 | h = {} 3 | a.each do |num| 4 | h[num] ? h.delete(num) : h[num] = 1 5 | end 6 | h.key(1) 7 | end 8 | 9 | puts solution([3, 5, 3, 7, 5, 10, 10, 3, 10, 3, 10]) -------------------------------------------------------------------------------- /03-1-tape_equilibrium.rb: -------------------------------------------------------------------------------- 1 | 2 | def solution(a) 3 | first = a[0] 4 | second = a[1..-1].reduce(&:+) 5 | min_diff = (first - second).abs 6 | (1..a.size - 1).each do |p| 7 | first += a[p] 8 | second -= a[p] 9 | diff = (first - second).abs 10 | min_diff = diff if diff < min_diff 11 | end 12 | min_diff 13 | end 14 | 15 | p solution([3, 1, 2, 4, 3]) -------------------------------------------------------------------------------- /03-2-frog_jump.rb: -------------------------------------------------------------------------------- 1 | def solution(x, y, d) 2 | jumps = (y - x) / d 3 | if (y - x) % d == 0 4 | return jumps 5 | else 6 | return jumps + 1 7 | end 8 | end -------------------------------------------------------------------------------- /03-3-perm_missing_elem.rb: -------------------------------------------------------------------------------- 1 | def solution(a) 2 | n = a.length 3 | return 1 if n == 0 4 | # 1 + 2 + 3 + ... + n + 1 5 | sum_should_be = (n + 1) * (n + 2) / 2 6 | sum = a.reduce(&:+) 7 | sum_should_be - sum 8 | end -------------------------------------------------------------------------------- /04-1-perm_check.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | 3 | def solution(a) 4 | # A permutation is a sequence containing each 5 | # element from 1 to N once, and only once. 6 | n = a.length 7 | s = Set.new 8 | a.each do |num| 9 | return 0 if num > n 10 | s << num 11 | end 12 | return 1 if s.size == n 13 | 0 14 | end -------------------------------------------------------------------------------- /04-2-frog_river_one.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | 3 | def solution(x, a) 4 | s = Set.new 5 | a.each_with_index do |leaf, time| 6 | s << leaf 7 | return time if s.size == x 8 | end 9 | -1 10 | end -------------------------------------------------------------------------------- /04-3-missing_integer.js: -------------------------------------------------------------------------------- 1 | // https://app.codility.com/demo/results/demoRAHZDF-A5A/ 2 | 3 | // write your code in ES6 JavaScript (Node.js 8.9.4) 4 | function solution(A) { 5 | let max = Math.max(...A); 6 | if (max <= 0) { 7 | return 1; 8 | } // Handles case of all numbers in array <= 0 9 | 10 | const freq = []; 11 | let answer; 12 | 13 | for (let i = 0; i < A.length; i++) { 14 | if (A[i] >= 0) { // Ignore all negative #s. 15 | freq[A[i]] = 1; // Track positive #s in frequency table. 16 | } 17 | } 18 | 19 | // For A = [1, 3, 6, 4, 1, 2], freq = [undefined, 1, 1, 1, 1, undefined, 1] 20 | 21 | for (let i = 1; i <= max; i++) { // Start with 1, since freq[0] could be undefined 22 | if (freq[i] === undefined) { 23 | answer = i; 24 | break; 25 | } 26 | } 27 | return answer || max + 1; // if answer is undefined, return max + 1 28 | } 29 | -------------------------------------------------------------------------------- /04-3-missing_integer.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/demoKZNNAB-Q6B/ 2 | require 'rspec/autorun' 3 | 4 | def solution1(a) 5 | n = a.length 6 | counts = Array.new(n + 1, 0) 7 | a.each do |num| 8 | counts[num] = 1 if num.between?(1, n + 1) 9 | end 10 | 11 | counts.length.times do |count| 12 | next_count = count + 1 13 | return next_count if counts[next_count] == 0 14 | end 15 | n + 1 16 | end 17 | 18 | 19 | # For answer, just care about first n positive integers. 20 | # Ignore any numbers < 1 or > n. 21 | 22 | def solution(a) 23 | n = a.size 24 | saw = Array.new(n) 25 | 26 | n.times do |i| 27 | if a[i].between?(1, n) 28 | saw[a[i] - 1] = 1 29 | end 30 | end 31 | 32 | saw.size.times do |anum| 33 | return anum + 1 if saw[anum].nil? 34 | end 35 | 36 | n + 1 37 | end 38 | 39 | describe 'Missing Integer' do 40 | it 'Finds smallest positive integer that does not occur in a given sequence.' do 41 | expect(solution([5])).to eq 1 42 | expect(solution([-1000000, 1000000])).to eq 1 43 | expect(solution([1, 1, 1])).to eq 2 44 | expect(solution([3, 3, 3])).to eq 1 45 | expect(solution([-1, -3])).to eq 1 46 | expect(solution([1, 2, 3])).to eq 4 47 | expect(solution([1, 3, 6, 4, 1, 2])).to eq 5 48 | expect(solution([6, 5, 3, 2, 1])).to eq 4 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /04-4-max_counters.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingYQCNE6-6HN/ 2 | 3 | def solution(n, a) 4 | counter = Array.new(n, 0) 5 | max_to_set = 0 6 | max_now = 0 7 | a.each do |num| 8 | if num == n + 1 9 | max_to_set = max_now 10 | else # 1 <= num <= n 11 | counter[num - 1] = max_to_set if counter[num - 1] < max_to_set 12 | counter[num - 1] += 1 13 | max_now = counter[num - 1] if counter[num - 1] > max_now 14 | end 15 | # p counter 16 | end 17 | counter.size.times do |i| 18 | counter[i] = max_to_set if counter[i] < max_to_set 19 | end 20 | counter 21 | end -------------------------------------------------------------------------------- /05-1-count_div.rb: -------------------------------------------------------------------------------- 1 | # count number of integers divisible by k in range [a..b]. 2 | 3 | def solution(a, b, k) 4 | if (a % k == 0) 5 | return (b - a) / k + 1 6 | else 7 | return (b - (a - a % k)) / k 8 | end 9 | end -------------------------------------------------------------------------------- /05-2-passing_cars.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingY6JNCP-TWA/ 2 | 3 | def solution(a) 4 | west = 0 # cars going west 5 | passing = 0 # pairs of passing cars 6 | a.reverse_each do |direction| 7 | if direction == 0 # east 8 | # add cars that have passed west so far 9 | passing += west 10 | else 11 | west += 1 12 | end 13 | end 14 | return -1 if passing > 1000000000 15 | passing 16 | end -------------------------------------------------------------------------------- /05-3-genomic_range_query.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingVHF4TQ-QC5/ 2 | # https://codility.com/programmers/lessons/3 3 | 4 | def solution(s, p, q) 5 | genoms = Array.new(3) {Array.new(s.length + 1, 0)} 6 | s.length.times do |i| 7 | a, c, g = 0, 0, 0 8 | a = 1 if s[i] == 'A' 9 | c = 1 if s[i] == 'C' 10 | g = 1 if s[i] == 'G' 11 | # prefix sums: 12 | # How many of each letter have we seen so far, left to right? 13 | genoms[0][i + 1] = genoms[0][i] + a 14 | genoms[1][i + 1] = genoms[1][i] + c 15 | genoms[2][i + 1] = genoms[2][i] + g 16 | end 17 | 18 | result = [] 19 | p.length.times do |i| 20 | from = p[i] 21 | # we add 1 to q[i], because our genoms[0][0], genoms[1][0] and genoms[2][0] 22 | # have 0 values by default, so look above genoms[0][i+1] = genoms[0][i] + a; 23 | to = q[i] + 1 24 | # count total: finds # of each letter between indices 'from' and 'to' 25 | # If we see at least 1 of that letter, we're done, because we seek 26 | # minimum value letter. Put in result. 27 | if genoms[0][to] - genoms[0][from] > 0 # Do we see at least 1 A? 28 | result[i] = 1 29 | elsif genoms[1][to] - genoms[1][from] > 0 # Do we see at least 1 C? 30 | result[i] = 2 31 | elsif genoms[2][to] - genoms[2][from] > 0 # Do we see at least 1 G? 32 | result[i] = 3 33 | else 34 | result[i] = 4 35 | end 36 | end 37 | result 38 | end 39 | 40 | # p solution('CAGCCTA', [2, 5, 0], [4, 5, 6]) 41 | 42 | # O(n) 43 | def prefix_sums(arr) 44 | n = arr.length 45 | p = [0] 46 | (1..n).each do |k| 47 | p[k] = p[k - 1] + arr[k - 1] 48 | puts "p[#{k}]= #{p[k]}" 49 | end 50 | p 51 | end 52 | 53 | # total of 1 slice, O(1) 54 | # the totals of m slices [x..y] such that 0 <= from(or x) <= to(or y) < n, 55 | # where the total is the sum ax + ax+1 + . . . + ay−1 + ay. 56 | def count_total(p, from, to) 57 | p[to + 1] - p[from] 58 | end -------------------------------------------------------------------------------- /05-4-min_avg_2_slice.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingYU359Z-X8P/ 2 | 3 | def solution(a) 4 | # codesays.com/2014/solution-to-min-avg-two-slice-by-codility 5 | # Says 2 and 3 element slices MUST contain the min average for whole array. 6 | # No need to check bigger slices. 7 | min_avg = 10000 8 | min_start_pos = 0 9 | (0..a.length - 3).each do |i| 10 | # All 2-element slices: 11 | two_avg = (a[i] + a[i + 1]) / 2.0 12 | # All 3-element slices: 13 | three_avg = (a[i] + a[i + 1] + a[i + 2]) / 3.0 14 | avg = [two_avg, three_avg].min 15 | if avg < min_avg 16 | min_avg = avg 17 | min_start_pos = i 18 | end 19 | end 20 | avg = (a[-1] + a[-2]) / 2.0 21 | min_start_pos = a.length - 2 if avg < min_avg 22 | min_start_pos 23 | end -------------------------------------------------------------------------------- /06-1-distinct.rb: -------------------------------------------------------------------------------- 1 | # 3 solutions 2 | 3 | require 'set' 4 | def solution(a) 5 | s = Set.new 6 | a.each do |num| 7 | s << num 8 | end 9 | s.size 10 | end 11 | 12 | # without using Set 13 | def solution(a) 14 | distinct = a.length == 0 ? 0 : 1 15 | a.sort! 16 | (1..a.length - 1).each do |i| 17 | next if a[i] == a[i - 1] 18 | distinct += 1 19 | end 20 | distinct 21 | end 22 | 23 | p solution([2, 1, 1, 2, 3, 1]) 24 | 25 | # https://codility.com/demo/results/demo855EPB-CXK/ 26 | # Time complexity: O(N*log N) 27 | def solution(a) 28 | h = {} 29 | a.each do |num| 30 | h[num] ? h[num] += 1 : h[num] = 1 31 | end 32 | h.keys.count 33 | end 34 | 35 | p solution([1,3,3,2,1,5,6,4,5]) -------------------------------------------------------------------------------- /06-2-triangle.rb: -------------------------------------------------------------------------------- 1 | # https://codesays.com/2014/solution-to-triangle-by-codility/ 2 | 3 | def solution(a) 4 | return 0 if a.length < 3 5 | a.sort! 6 | 7 | # After sorting, a[i] < a[i + 1] < a[i + 2] 8 | 9 | # These must be true: 10 | # a[i+1] + a[i+2] > a[i] 11 | # a[i] + a[i+2] > a[i+1] 12 | 13 | # Thus all we must check is if 14 | # a[i] + a[i + 1] > a[i + 2] 15 | 16 | (0..a.length - 3).each do |i| 17 | return 1 if a[i] + a[i + 1] > a[i + 2] 18 | end 19 | 20 | # If a[i] + a[i + 1] > a[i + 2], comments below show 21 | # the original indices P, Q, R should have P < Q < R 22 | # for at least 1 of those 3 inequalities 23 | 0 24 | end 25 | 26 | # a = [10, 2, 5, 1, 8, 20] 27 | # sorted = [1, 2, 5-2, 8-4, 10-0, 20] 28 | 29 | # 5 + 8 > 10 => 2, 4, 0 30 | # 10 + 5 > 8 => 0, 2, 4 31 | # 8 + 10 > 5 => 4, 0, 2 32 | # p solution(a) 33 | 34 | # a = [10, 50, 5, 1] 35 | # p solution(a) 36 | 37 | # n = 4, m = 3, 1 38 | # a[2] + a[2 + 3] > a[2 + 4] 39 | 40 | # a[2] <= a[2 + 3 - 1] = a[4] YES!! 41 | # a[2 + 3 + 1] <= a[2 + 4] YES!!! 42 | 43 | # a[2 + 3 - 1] + a[2 + 3] >= a[2] + a[2 + 3] > a[2 + 4] >= a[2 + 3 + 1] 44 | # a[4] + a[5] >= a[2] + a[5] > a[6] >= a[6] 45 | 46 | # 1) A[index]+A[index+m] > A[index+n] 47 | # 2) A[index+m-1] >= A[index] 48 | # 3) A[index + n] >= A[index+m+1] 49 | 50 | # 1) + 2): A[index+m] + A[index+m-1] >= A[index+n] 51 | # + 3): A[index+m-1] + A[index+m] >= A[index+m+1] 52 | -------------------------------------------------------------------------------- /06-3-max_product_of_3.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingJN2VE5-5Q4/ 2 | 3 | def solution(a) 4 | a.sort! 5 | # max product, after sort, is either prod of last 3 numbers, 6 | # or prod of first 2 (negative) numbers * last positive number 7 | [a[-3] * a[-2] * a[-1], a[0] * a[1] * a[-1]].max 8 | end 9 | 10 | a = [-3, 1, 2, -2, 5, 6] 11 | 12 | p solution(a) -------------------------------------------------------------------------------- /06-4-disc_intersections.rb: -------------------------------------------------------------------------------- 1 | def solution(a) 2 | upper, lower = [], [] 3 | low_index = 0 4 | intersections = 0 5 | a.each_with_index do |radius, center| 6 | upper << center + radius 7 | lower << center - radius 8 | end 9 | 10 | upper.sort! 11 | lower.sort! 12 | 13 | upper.each_with_index do |up, up_index| 14 | while (low_index < a.size && up >= lower[low_index]) 15 | # low_index only goes up, not down. Avoids double counting 16 | low_index += 1 17 | end 18 | intersections += (low_index - up_index - 1) 19 | end 20 | if intersections > 10_000_000 21 | -1 22 | else 23 | intersections 24 | end 25 | end 26 | 27 | # This got 100% correct, but 60% performance = O(n*n) 28 | # def solution(a) 29 | # intersections = 0 30 | # ranges = [] 31 | # a.each_with_index do |radius, center| 32 | # ranges << [center - radius, center + radius] 33 | # end 34 | # puts "ranges: #{ranges} \n\n" 35 | 36 | # (0..a.length - 2).each do |center| 37 | # left_edge1 = center - a[center] 38 | # right_edge1 = center + a[center] 39 | 40 | # (center + 1..a.length - 1).each do |center2| 41 | # puts "center: #{center}, center2: #{center2}" 42 | # if center2 != center 43 | # left_edge2 = center2 - a[center2] 44 | # right_edge2 = center2 + a[center2] 45 | 46 | # if left_edge2 <= right_edge1 47 | # intersections += 1 48 | # puts "Disc #{center} intersects Disc #{center2}. Center #{center} between #{left_edge2} and #{right_edge2}\n\n" 49 | # end 50 | # end 51 | # end 52 | # end 53 | # intersections 54 | # end 55 | 56 | 57 | a = [1, 5, 2, 1, 4, 0] 58 | 59 | p solution(a) 60 | 61 | # ranges: [[-1, 1], [-4, 6], [0, 4], [2, 4], [0, 8], [5, 5]] 62 | 63 | # center: 0, center2: 1 64 | # Disc 0 intersects Disc 1. Center 0 between -4 and 6 65 | 66 | # center: 0, center2: 2 67 | # Disc 0 intersects Disc 2. Center 0 between 0 and 4 68 | 69 | # center: 0, center2: 3 70 | # center: 0, center2: 4 71 | # Disc 0 intersects Disc 4. Center 0 between 0 and 8 72 | 73 | # center: 0, center2: 5 74 | # center: 1, center2: 2 75 | # Disc 1 intersects Disc 2. Center 1 between 0 and 4 76 | 77 | # center: 1, center2: 3 78 | # Disc 1 intersects Disc 3. Center 1 between 2 and 4 79 | 80 | # center: 1, center2: 4 81 | # Disc 1 intersects Disc 4. Center 1 between 0 and 8 82 | 83 | # center: 1, center2: 5 84 | # Disc 1 intersects Disc 5. Center 1 between 5 and 5 85 | 86 | # center: 2, center2: 3 87 | # Disc 2 intersects Disc 3. Center 2 between 2 and 4 88 | 89 | # center: 2, center2: 4 90 | # Disc 2 intersects Disc 4. Center 2 between 0 and 8 91 | 92 | # center: 2, center2: 5 93 | # center: 3, center2: 4 94 | # Disc 3 intersects Disc 4. Center 3 between 0 and 8 95 | 96 | # center: 3, center2: 5 97 | # center: 4, center2: 5 98 | # Disc 4 intersects Disc 5. Center 4 between 5 and 5 99 | 100 | # 11 -------------------------------------------------------------------------------- /07-1-brackets_spec.rb: -------------------------------------------------------------------------------- 1 | # https://app.codility.com/demo/results/trainingJW3BU4-E8H/ 2 | 3 | def solution(s) 4 | stack = [] 5 | hash = {'{' => '}', '(' => ')', '[' => ']'} 6 | s.each_char do |char| 7 | if hash[stack.last] == char 8 | stack.pop 9 | else 10 | stack << char 11 | end 12 | end 13 | 14 | return 0 if stack.size > 0 15 | 1 16 | end 17 | 18 | # Golang version: 19 | # https://www.hackerrank.com/challenges/balanced-brackets/problem 20 | 21 | # func isBalanced(s string) string { 22 | # stack := []string{} 23 | # hash := map[string]string{ 24 | # "(": ")", 25 | # "[": "]", 26 | # "{": "}", 27 | # } 28 | # var top string 29 | 30 | # for _, char := range strings.Split(s, "") { 31 | # if len(stack) > 0 { 32 | # top = stack[len(stack)-1] 33 | # } else { 34 | # top = "" 35 | # } 36 | # if hash[top] == char { 37 | # stack = stack[:len(stack)-1] // pop off stack 38 | # } else { 39 | # stack = append(stack, char) 40 | # } 41 | # } 42 | # if len(stack) > 0 { 43 | # return "NO" 44 | # } 45 | # return "YES" 46 | # } 47 | 48 | describe '#brackets' do 49 | it 'returns 1 if brackets are properly nested' do 50 | expect(solution("{[()()]}")).to eq 1 51 | end 52 | 53 | it 'returns 0 if they are not' do 54 | expect(solution("([)()]")).to eq 0 55 | end 56 | end 57 | 58 | # char = { 59 | # stack: ["{"] 60 | # char = [ 61 | # stack: ["{", "["] 62 | # char = ( 63 | # stack: ["{", "[", "("] 64 | # char = ) 65 | # pop ( ) 66 | # stack: ["{", "["] 67 | # char = ( 68 | # stack: ["{", "[", "("] 69 | # char = ) 70 | # pop ( ) 71 | # stack: ["{", "["] 72 | # char = ] 73 | # pop [ ] 74 | # stack: ["{"] 75 | # char = } 76 | # pop { } 77 | # stack: [] 78 | # final stack: [] 79 | # 1 80 | # char = ( 81 | # stack: ["("] 82 | # char = [ 83 | # stack: ["(", "["] 84 | # char = ) 85 | # stack: ["(", "[", ")"] 86 | # char = ( 87 | # stack: ["(", "[", ")", "("] 88 | # char = ) 89 | # pop ( ) 90 | # stack: ["(", "[", ")"] 91 | # char = ] 92 | # stack: ["(", "[", ")", "]"] 93 | # final stack: ["(", "[", ")", "]"] 94 | # 0 95 | -------------------------------------------------------------------------------- /07-2-fish.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingRHJT3Z-MTV/ 2 | 3 | # If P < Q, then fish P is initially upstream of fish Q 4 | # Array B contains the directions of the fish. It contains only 0s and/or 1s, where: 5 | # 0 represents a fish flowing upstream, 6 | # 1 represents a fish flowing downstream. 7 | 8 | # Two fish P and Q meet each other when P < Q, B[P] = 1 and B[Q] = 0, and there are no living fish between them. After they meet: 9 | 10 | # If A[P] > A[Q] then P eats Q, and P will still be flowing downstream, 11 | # If A[Q] > A[P] then Q eats P, and Q will still be flowing upstream. 12 | 13 | # Q: calculate the number of fish that will stay alive. 14 | 15 | # a = [4, 3, 2, 1, 5] 16 | # b = [0, 1, 0, 0, 0] 17 | 18 | a = [6, 3, 2, 1, 4, 5] 19 | b = [0, 1, 0, 1, 0, 0] 20 | 21 | # [[6, 0]] 22 | # [[6, 0]] << [3, 1] 23 | # [[6, 0], [3, 1]] 24 | # [[6, 0], [3, 1]] << [2, 0]? NO! - fish to right is eaten 25 | # [[6, 0], [3, 1]] << [1, 1]? YES - no one dies 26 | # [[6, 0], [3, 1], [1, 1]] 27 | # [[6, 0], [3, 1], [1, 1]] << [4, 0]? YES - fish to left is eaten 28 | # [[6, 0], [3, 1]] ans.pop 29 | # [[6, 0]] - ans.pop, then stops 30 | # [[6, 0], [4, 0]] 31 | # [[6, 0], [4, 0]] << [5, 0]? YES - no one dies 32 | # [[6, 0], [4, 0], [5, 0]] 33 | 34 | # Answer: 3 fish are left 35 | 36 | # 100% correctness, 100% performance. O(n). 37 | def solution(a, b) 38 | stack = [[a[0], b[0]]] 39 | if a.size > 1 40 | (1..a.size - 1).each do |i| 41 | if stack.last[1] <= b[i] 42 | stack << [a[i], b[i]] # no one dies 43 | next 44 | end 45 | while stack.size > 0 && stack.last[0] < a[i] && stack.last[1] > b[i] # fish to left dies 46 | stack.pop 47 | end 48 | if stack.size == 0 || stack.last[1] == b[i] 49 | stack << [a[i], b[i]] 50 | end 51 | end 52 | end 53 | stack.size 54 | end 55 | 56 | p solution(a, b) 57 | 58 | # 100% correctness, but O(n*n) 59 | 60 | # def solution(a, b) 61 | # found_one_and_zero = true 62 | # while found_one_and_zero 63 | # found_one_and_zero = false 64 | # (0..b.size - 2).each do |i| 65 | # if b[i] == 1 && b[i + 1] == 0 66 | # found_one_and_zero = true 67 | # if a[i] > a[i + 1] 68 | # a[i + 1] = a[i] 69 | # b[i + 1]= 1 70 | # a[i], b[i] = nil, nil 71 | # else 72 | # a[i] = a[i + 1] 73 | # b[i] = 0 74 | # a[i + 1], b[i + 1] = nil, nil 75 | # end 76 | # end 77 | # puts "b: #{b}" 78 | # puts "a: #{a}" 79 | # end 80 | # puts "found_one_and_zero: #{found_one_and_zero}" 81 | # b.select! {|fish| !fish.nil?} 82 | # a.select! {|fish| !fish.nil?} 83 | # end 84 | # puts "b: #{b}" 85 | # b.size 86 | # end 87 | -------------------------------------------------------------------------------- /07-3-stonewall.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingUM6MGK-R7K/ 2 | 3 | # 0. Start at height 8. Block 1. 4 | # 1. H = 8 still. Block 1. Skip this H. Stack = [8] 5 | # 2. 8 > H = 5. Only keep stuff on stack if it's < H. Keep stack in ASCENDING ORDER. Pop 8. Stack = [5]. Block 2. 6 | # 3. 5 < H = 7. New height not used before. Stack = [5, 7]. Block 3. 7 | # 4. 7 < H = 9. New height not used before. Stack = [5, 7, 9]. Block 4. 8 | # 5. 9 > H = 8. Pop 9. 7 < 8, so keep it. Stack = [5, 7, 8]. Block 5. 9 | # 6. 8 > H = 7. Pop 8. Stack = [5, 7]. Since stack.last == H, move on. Block 5. 10 | # 7. 7 > H = 4. Pop 7 and 5. Stack = [4]. Block 6. 11 | # 8. 4 < H = 8. Stack [4, 8]. Block 7. 12 | 13 | def solution(h) 14 | blocks = 0 15 | stack = [] 16 | h.each do |height| 17 | #puts "height: #{height}, stack: #{stack}, blocks: #{blocks}" 18 | while stack.size > 0 && stack.last > height 19 | stack.pop 20 | end 21 | 22 | next if stack.size > 0 && stack.last == height 23 | stack << height 24 | blocks += 1 25 | end 26 | blocks 27 | end 28 | 29 | h = [8, 8, 5, 7, 9, 8, 7, 4, 8] 30 | puts "Min num of blocks: #{solution(h)}" 31 | 32 | # height: 8, stack: [], blocks: 0 33 | # height: 8, stack: [8], blocks: 1 34 | # height: 5, stack: [8], blocks: 1 35 | # height: 7, stack: [5], blocks: 2 36 | # height: 9, stack: [5, 7], blocks: 3 37 | # height: 8, stack: [5, 7, 9], blocks: 4 38 | # height: 7, stack: [5, 7, 8], blocks: 5 39 | # height: 4, stack: [5, 7], blocks: 5 40 | # height: 8, stack: [4], blocks: 6 41 | # Answer: 7 blocks 42 | 43 | # O(n*n) 44 | # def slow_solution(h) #<= works 100%, but 64% Codility performance 45 | # stack = [h.shift] 46 | # blocks = 1 47 | # h.each do |height| 48 | # #puts "height: #{height} " 49 | # next if height == stack.last 50 | # if stack.include?(height) 51 | # last_time = stack.rindex(height) 52 | # any_height_less = stack[last_time..-1].any?{|num| num < height} 53 | # if any_height_less 54 | # puts "see old height less than current, at index #{last_time}" 55 | # blocks += 1 56 | # end 57 | # else 58 | # blocks += 1 59 | # end 60 | # stack << height 61 | # #puts "stack: #{stack}, blocks: #{blocks}" 62 | # end 63 | # blocks 64 | # end 65 | -------------------------------------------------------------------------------- /07-4-nesting.rb: -------------------------------------------------------------------------------- 1 | # like 7-1, but don't need stack 2 | 3 | def solution(s) 4 | parens = 0 5 | s.split('').each do |char| 6 | if char == '(' 7 | parens += 1 8 | else 9 | parens -= 1 10 | return 0 if parens < 0 # fails without this line 11 | end 12 | end 13 | if parens == 0 14 | 1 15 | else 16 | 0 17 | end 18 | end -------------------------------------------------------------------------------- /08-1-dominator.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/training9V65T3-X3Q/ 2 | 3 | def solution(a) 4 | leader = -1 5 | stack = [] 6 | a.each do |num| 7 | stack << num 8 | if stack.size < 2 9 | next 10 | end 11 | if stack.last != stack[-2] 12 | stack.pop(2) 13 | end 14 | end 15 | if a.count(stack.last) > a.size / 2 16 | leader = stack.last 17 | end 18 | return a.index(leader) if leader != -1 19 | -1 20 | end -------------------------------------------------------------------------------- /08-2-equi_leader.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingAG7YKJ-6QQ/ 2 | # If you have array A with leader z (leader = unique element that occurs more than in half of array), then for every separation of A into A[0..i), A[i..n), if they have the same leader, then the leader of both is z. 3 | 4 | # This follows from noticing that if x is a leader in A[0..i), then it occurs more than ⌈i/2⌉ elements, and similarly, if y is the leader of A[i..n), then y occurs more than ⌈(n−i)/2⌉. 5 | 6 | # If both slices have leader z, it must occur more than ⌈n/2⌉, which means that y=x as required. 7 | 8 | a = [4, 3, 4, 4, 4, 2] 9 | 10 | # O(n) 11 | def fast_solution(a) 12 | equileaders = 0 13 | 14 | # 1. Find leader candidate 15 | candidate = a.first 16 | count = 1 17 | a.size.times do |i| 18 | a[i] == candidate ? count += 1 : count -= 1 19 | if count == 0 20 | count = 1 21 | candidate = a[i] 22 | end 23 | end 24 | 25 | # 2. Is candidate a leader? 26 | total_leaders = a.count(candidate) 27 | if total_leaders <= a.size / 2 28 | return 0 # no leaders in array 29 | else 30 | leader = candidate 31 | end 32 | 33 | # 3. Loop through all possible left/right divisions of array. 34 | # From left/right leader counts, count which meet criteria of equileaders. 35 | left_leader_count = 0 36 | a.size.times do |i| 37 | left_leader_count += 1 if a[i] == leader 38 | right_leader_count = total_leaders - left_leader_count 39 | 40 | # i + 1 = length of left side 41 | # a.size - 1 - i = length of right side 42 | # (i + 1) + (a.size - 1 - i) = a.size 43 | if left_leader_count > (i + 1) / 2 && 44 | right_leader_count > (a.size - 1 - i) / 2 45 | equileaders += 1 46 | end 47 | end 48 | equileaders 49 | end 50 | 51 | #_________________________ 52 | def leader(arr) 53 | leader = nil 54 | stack = [] 55 | arr.each do |num| 56 | stack << num 57 | if stack.size < 2 58 | next 59 | end 60 | if stack.last != stack[-2] 61 | stack.pop(2) 62 | end 63 | end 64 | if arr.count(stack.last) > arr.size / 2 65 | leader = stack.last 66 | end 67 | return leader if leader 68 | end 69 | 70 | def slow_solution(a) 71 | equi = 0 72 | a.size.times do |i| 73 | left = leader(a[0..i]) 74 | right = leader(a[i + 1..-1]) 75 | if (left && right) && (left == right) 76 | equi += 1 77 | end 78 | end 79 | equi 80 | end 81 | -------------------------------------------------------------------------------- /09-1-max_slice_sum.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/training2BXZUD-TDS/ 2 | 3 | def solution(array) 4 | max_ending = max_slice = array.first 5 | array.shift # delete 1st element 6 | array.each do |element| # start loop from 2nd element 7 | max_ending = [element, max_ending + element].max 8 | max_slice = [max_ending, max_slice].max 9 | end 10 | max_slice 11 | end 12 | 13 | # If array = [5] (1 element), array.shift empties array, 14 | # so it skips loop. 15 | 16 | # If array = [-10, -2, -5] (all negative), 17 | # max_slice = -2 (max negative value) 18 | 19 | # If array = [-2, 1, 2] (1st number < 0) 20 | # max_ending = -2 21 | # In loop, max_ending = [1, -2 + 1].max = 1, so we skip 1st number 22 | -------------------------------------------------------------------------------- /09-2-max_double_slice_sum.rb: -------------------------------------------------------------------------------- 1 | 2 | # O(n*n*n) 3 | def slow_solution(a) 4 | sum = 0 5 | new_array = [] 6 | (0..a.length-3).each do |x| 7 | (x+1..a.length-2).each do |y| 8 | (y+1..a.length-1).each do |z| 9 | puts "x: #{x}, y: #{y}, z: #{z}" 10 | new_array = a[x+1..y-1] + a[y+1..z-1] 11 | puts "new_array: #{new_array}" 12 | sum = [max_slice(new_array), sum].max 13 | puts "sum: #{sum}" 14 | end 15 | end 16 | end 17 | sum 18 | end 19 | 20 | # puts slow_solution(a) 21 | 22 | def max_slice(array) 23 | max_ending = max_slice_sum = 0 24 | array.each do |a| 25 | max_ending = [0, max_ending + a].max 26 | max_slice_sum = [max_ending, max_slice_sum].max 27 | end 28 | max_slice_sum 29 | end 30 | 31 | a = [3, 2, 6, -1, 4, 5, -1, 2] 32 | # O(n) 33 | def fast_solution(a) 34 | left2right = [0] * a.size 35 | right2left = [0] * a.size 36 | # make array of max_endings from left to right 37 | 1.upto(a.size - 2) do |i| 38 | left2right[i] = [0, left2right[i - 1] + a[i]].max 39 | end 40 | 41 | # make array of max_endings from right to left 42 | (a.size - 2).downto(1) do |i| 43 | right2left[i] = [0, right2left[i + 1] + a[i]].max 44 | end 45 | 46 | max_dbl_sum = 0 47 | (1..a.size - 2).each do |i| 48 | # add both to get max_slice 49 | max_sum = left2right[i - 1] + right2left[i + 1] 50 | max_dbl_sum = [max_dbl_sum, max_sum].max 51 | end 52 | max_dbl_sum 53 | end 54 | 55 | p fast_solution(a) 56 | 57 | # light2right: [0, 2, 8, 7, 11, 16, 15, 0] 58 | # right2left: [0, 16, 14, 8, 9, 5, 0, 0] 59 | 60 | # Ex: If we slice original array at Y = 2 61 | # maxending from L to R of index 1 = 2 62 | # maxending from R to L of index 3..6 = 8 63 | # max_slice: 2 + 8 64 | 65 | # Ex: If we slice original array at Y = 4 66 | # maxending from L to R of index 1..3 = 7 67 | # maxending from R to L of index 5..6 = 5 68 | # max_slice: 7 + 5 69 | -------------------------------------------------------------------------------- /09-3-max_profit.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/training5EEH22-NT7/ 2 | # a = [25, 23, 30, 20, 16] 3 | # max_profit = 7 4 | # profit = -20 5 | # price = 16 6 | # min = 16 7 | # max = 0 8 | 9 | # If see a new min, reset max to 0 10 | 11 | # O(n) 12 | def solution(a) 13 | return 0 if a.empty? 14 | min = a.shift 15 | max = a.first 16 | profit = max - min 17 | max_profit = profit 18 | a.each do |price| 19 | if price > max 20 | max = price 21 | elsif price < min 22 | min = price 23 | max = 0 # this price and all later prices won't be used for max_profit, until we see a price > max 24 | end 25 | profit = max - min 26 | max_profit = [max_profit, profit].max 27 | end 28 | max_profit > 0 ? max_profit : 0 29 | end -------------------------------------------------------------------------------- /10-1-count_factors.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingNW3CQ4-8S7/ 2 | # O(sqrt(n)) time. O(1) space. 3 | def solution(n) 4 | factors = 0 5 | (1..Math.sqrt(n)).each do |i| 6 | if n % i == 0 7 | factors += i == Math.sqrt(n) ? 1 : 2 8 | end 9 | end 10 | factors 11 | end -------------------------------------------------------------------------------- /10-2-min_perimeter_rectangle.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/training2G36QV-PWY/ 2 | # O(sqrt(n)) time. O(1) space. 3 | def solution(area) 4 | min_perimeter = 2 * (1 + area) 5 | (1..Math.sqrt(area)).each do |width| 6 | if area % width == 0 7 | length = area / width 8 | else 9 | next 10 | end 11 | perimeter = 2 * (length + width) 12 | min_perimeter = [perimeter, min_perimeter].min 13 | end 14 | min_perimeter 15 | end -------------------------------------------------------------------------------- /10-3-peaks.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingY67YP5-4QB/ 2 | # require 'minitest/autorun' <--- for Minitest 3 | 4 | # O(n*n). 100% correct, 20% performance. 5 | def slow_solution(a) 6 | return 0 if [1, 2].include?(a.size) 7 | peaks = [] 8 | blocks = 0 9 | length = a.size 10 | 11 | (1..length - 2).each do |i| 12 | if a[i] > a[i - 1] && a[i] > a[i + 1] 13 | peaks << i 14 | end 15 | end 16 | 17 | peaks.size.downto(1) do |b| 18 | next unless length % b == 0 19 | num_peaks = 0 20 | bsize = length / b 21 | b.times do |c| 22 | if !peaks.any? {|p| p.between?(c * bsize, (c+1) * bsize - 1)} 23 | # 0 ..1 * bsize - 1 24 | # 1 * bsize..2 * bsize - 1 25 | # 2 * bsize..3 * bsize - 1 26 | break 27 | else 28 | num_peaks += 1 29 | end 30 | end 31 | return b if num_peaks == b 32 | end 33 | blocks 34 | end 35 | 36 | # O(n*log(log n)) time. O(n) space. 37 | def fast_solution(a) 38 | peaks = [] 39 | blocks = 0 40 | length = a.size 41 | 42 | # find indices of all peaks 43 | (1..length - 2).each do |i| 44 | if a[i] > a[i - 1] && a[i] > a[i + 1] 45 | peaks << i 46 | end 47 | end 48 | 49 | # max possible # of blocks = # of peaks 50 | peaks.size.downto(1) do |blocks| 51 | next unless length % blocks == 0 52 | block_size = length / blocks 53 | block_no = 0 54 | 55 | 56 | peaks.each do |peak| 57 | if peak / block_size > block_no 58 | # current block doesn't have peak. try smaller # of blocks. 59 | break 60 | end 61 | if peak / block_size == block_no 62 | # current block has peak. see if next block has peak. 63 | block_no += 1 64 | end 65 | end 66 | 67 | return blocks if block_no == blocks 68 | end 69 | end 70 | 71 | # RSpec tests: 72 | 73 | describe '#solution' do 74 | it 'finds max # of blocks into which array may be divided' do 75 | expect(solution([5, 6])).to eq(0) 76 | expect(solution([4, 6, 2])).to eq(1) 77 | expect(solution([1, 10, 9, 6, 20])).to eq(1) 78 | expect(solution([1, 2, 1, 2, 1, 2])).to eq(2) 79 | expect(solution([1, 2, 3, 4, 3, 4, 1, 2, 3, 4, 6, 2])).to eq(3) 80 | expect(solution([1, 5, 3, 4, 3, 4, 1, 0, 1, 4, 6, 7])).to eq(1) 81 | expect(solution([1, 5, 3, 4, 3, 4, 1, 2, 1, 4, 6, 7])).to eq(2) 82 | end 83 | end 84 | 85 | # Minitest style: 86 | 87 | # class TestPeaks < Minitest::Test 88 | # def test_peaks 89 | # assert_equal(0, solution([5, 6])) 90 | # end 91 | # end 92 | 93 | # Your test case: [5, 6] 94 | # Returned value: 0 95 | 96 | # Your test case: [4, 6, 2] 97 | # Returned value: 1 98 | # peaks = [1] 99 | 100 | # Your test case: [1, 10, 9, 6, 20] 101 | # Returned value: 1 102 | # peaks = [1] 103 | 104 | # Your test case: [1, 2, 1, 2, 1, 2] 105 | # Returned value: 2 106 | # peaks = [1, 3] 107 | 108 | # Example test: [1, 2, 3, 4, 3, 4, 1, 2, 3, 4, 6, 2] 109 | # Returned value: 3 110 | # Peaks = [3, 5, 10] 111 | 112 | # Your test case: [1, 5, 3, 4, 3, 4, 1, 0, 1, 4, 6, 7] 113 | # Returned value: 1 114 | # peaks = [1, 3, 5] 115 | 116 | # Your test case: [1, 5, 3, 4, 3, 4, 1, 2, 1, 4, 6, 7] 117 | # Returned value: 2 118 | # peaks = [1, 3, 5, 7] -------------------------------------------------------------------------------- /10-4-flags.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingAWFUGJ-A6U/ 2 | # a = [1, 5, 3, 4, 3, 4, 1, 2, 3, 4, 6, 2] 3 | 4 | def find_peaks(a) 5 | peaks = [false] 6 | (1..a.size - 2).each do |i| 7 | if a[i] > a[i - 1] && a[i] > a[i + 1] 8 | peaks << true 9 | else 10 | peaks << false 11 | end 12 | end 13 | peaks 14 | end 15 | 16 | def next_peak(a) 17 | peaks = find_peaks(a) 18 | nexxt = [0] * a.size 19 | nexxt[a.size - 1] = -1 20 | (a.size - 2).downto(0).each do |i| 21 | if peaks[i] 22 | nexxt[i] = i 23 | else 24 | nexxt[i] = nexxt[i + 1] 25 | end 26 | end 27 | nexxt 28 | end 29 | 30 | # check if we can set f flags 31 | # def check(f, a) 32 | # # 1. find all peaks 33 | # peaks = find_peaks(a) 34 | # pos = 0 35 | # flags = f 36 | # while pos < a.size && flags > 0 37 | # if peaks[pos] 38 | # flags -= 1 39 | # pos += f 40 | # else 41 | # pos += 1 42 | # end 43 | # end 44 | # flags == 0 45 | # end 46 | 47 | # O(n) 48 | def solution(a) 49 | nexxt = next_peak(a) 50 | f = 1 # can we set f flags? 51 | max_flags = 0 52 | # (f-1) * f = max distance between first and last flags 53 | while (f - 1) * f <= a.size 54 | pos = 0 55 | flags_so_far = 0 56 | while pos < a.size && flags_so_far < f 57 | pos = nexxt[pos] 58 | break if pos == -1 59 | flags_so_far += 1 60 | pos += f 61 | end 62 | max_flags = [max_flags, flags_so_far].max 63 | f += 1 64 | end 65 | max_flags 66 | end 67 | 68 | describe 'Flags' do 69 | it '#solution' do 70 | expect(solution([5])).to eq 0 71 | expect(solution([1, 5, 3, 4, 3, 4, 1, 2, 3, 4, 6, 2])).to eq 3 72 | expect(solution([1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1])).to eq 4 73 | expect(solution([1, 2, 1, 2, 1])).to eq 2 74 | expect(solution([1, 2, 1, 2, 1, 2, 1])).to eq 2 75 | expect(solution([1, 2, 1, 1, 2, 1, 1, 2, 1])).to eq 3 76 | expect(solution([1, 2, 1, 1, 2, 1, 2, 1])).to eq 2 77 | expect(solution([1, 1, 1, 1])).to eq 0 78 | expect(solution([1, 2, 2, 2, 2, 3, 2, 2, 2, 2, 1])).to eq 1 79 | expect(solution([1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1])).to eq 2 80 | expect(solution([1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1])).to eq 3 81 | expect(solution([1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1])).to eq 4 82 | expect(solution([1, 2, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1])).to eq 3 83 | end 84 | end -------------------------------------------------------------------------------- /11-1-count_semiprimes.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingBTPTGH-KEE/ 2 | 3 | # return array of primes between 2 and n 4 | def sieve(n) 5 | primes = [nil, nil] + (2..n).to_a 6 | i = 2 7 | while i * i <= n 8 | if primes[i] 9 | k = i * i 10 | while k <= n 11 | primes[k] = nil 12 | k += i 13 | end 14 | end 15 | i += 1 16 | end 17 | primes.compact 18 | end 19 | 20 | # 100% correctness, 20% performance 21 | def slow_solution(n, p, q) 22 | return [0] * p.size if n == 1 23 | ans = [] 24 | primes = sieve(n / 2) 25 | 26 | # 2. Find all semiprimes between 2 and n 27 | semiprimes = [] 28 | primes.each_with_index do |prime1, i| 29 | primes[i..-1].each do |prime2| 30 | prod = prime1 * prime2 31 | break if prod > n 32 | semiprimes << prod 33 | end 34 | end 35 | semiprimes.sort! 36 | 37 | p.size.times do |i| 38 | ans << semiprimes.count { |semi| semi.between?(p[i], q[i]) } 39 | end 40 | ans 41 | end 42 | 43 | # For n = 26, first few primes = 2, 3, 5, 7, 11, 13. 44 | # A semiprime is the product of two (not necessarily distinct) prime numbers. 45 | # For n = 26, semiprimes are 4, 6, 9, 10, 14, 15, 21, 22, 25, 26. 46 | 47 | def fast_solution(n, p, q) 48 | return [0] * p.size if n == 1 49 | ans = [] 50 | # 1. Find primes between 2 and n/2. 51 | # Semiprime factors don't include > n/2 52 | primes = sieve(n / 2) 53 | 54 | # 2. Mark all semiprimes between 2 and n, with 1 55 | semiprimes = [0] * (n + 1) 56 | primes.each_with_index do |prime1, i| 57 | primes[i..-1].each do |prime2| 58 | prod = prime1 * prime2 59 | break if prod > n 60 | semiprimes[prod] = 1 61 | end 62 | end 63 | # p semiprimes 64 | # [0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1] 65 | 66 | # 3. Prefix sum. Rewrite semiprimes to track # of them up to index. 67 | (1..n).each do |i| 68 | semiprimes[i] += semiprimes[i - 1] 69 | end 70 | # p semiprimes 71 | # [0, 0, 0, 0, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 7, 8, 8, 8, 9, 10] 72 | 73 | ans = [] 74 | # 4. Loop through p, q, using answer to #2, and build answer array 75 | p.size.times do |i| 76 | # prefix sums trick. Sum of 1 slice, from p[i] to q[i] 77 | ans << semiprimes[q[i]] - semiprimes[p[i] - 1] 78 | end 79 | ans # [10, 4, 0] 80 | end 81 | 82 | # p fast_solution(1, [1, 1, 1], [1, 1, 1]) 83 | p fast_solution(26, [1, 4, 16], [26, 10, 20]) 84 | -------------------------------------------------------------------------------- /11-2-count_not_divisible.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingMQHP6E-KXY/ 2 | 3 | # O(log n) 4 | def prime_factors(n) 5 | factors = [] 6 | f = 2 7 | while f * f <= n 8 | if n % f == 0 9 | factors << f 10 | n /= f 11 | else 12 | f += 1 13 | end 14 | end 15 | factors << n 16 | factors 17 | end 18 | 19 | # O(n*n) 100% correct, 0% performance 20 | def slow_solution(a) 21 | ans = [] 22 | a.size.times do |i| 23 | primes = prime_factors(a[i]) # log n 24 | nondivisors = a - primes - [1, a[i]] 25 | ans << nondivisors.size - nondivisors.select {|nd| a[i] % nd == 0}.size 26 | end 27 | ans 28 | end 29 | 30 | # a = [3, 1, 2, 3, 6, 12] 31 | # O(n log n) 32 | def fast_solution(a) 33 | nondivisors = [] 34 | amax = a.max 35 | freq = Hash.new(0) 36 | divisors = {} 37 | 38 | # Frequency hash of each num 39 | # Initialize hash of divisors of each num 40 | a.each do |num| 41 | freq[num] += 1 42 | divisors[num] = num == 1 ? [1] : [1, num] 43 | end 44 | # freq: {3=>2, 1=>1, 2=>1, 6=>1, 12=>1} 45 | # divisors: {3=>[1, 3], 1=>[1], 2=>[1, 2], 6=>[1, 6], 12=>[1, 12]} 46 | 47 | # Sieve of Eratosthenes: Hash of divisors of each num 48 | div = 2 49 | while div * div <= amax 50 | num = div 51 | while num <= amax 52 | if divisors[num] && !divisors[num].include?(div) 53 | divisors[num] << div 54 | divisors[num] << num / div 55 | end 56 | num += div 57 | end 58 | div += 1 59 | end 60 | # divisors: {3=>[1, 3], 1=>[1], 2=>[1, 2], 6=>[1, 6, 2, 3], 12=>[1, 12, 2, 6, 3, 4]} 61 | 62 | a.each do |num| 63 | num_of_divisors = 0 64 | divisors[num].uniq.each do |div| 65 | num_of_divisors += freq[div] 66 | end 67 | nondivisors << a.size - num_of_divisors 68 | end 69 | nondivisors 70 | end 71 | 72 | p fast_solution([3, 1, 2, 3, 6, 12]) 73 | # nondivisors: [3, 5, 4, 3, 1, 0] 74 | # _________________________________ 75 | # freq: {3=>2, 1=>1, 2=>1, 6=>1, 12=>1} 76 | # divisors: {3=>[1, 3], 1=>[1], 2=>[1, 2], 6=>[1, 6], 12=>[1, 12]} 77 | # div: 2 78 | # num: 2 79 | # divisors[2]: [1, 2] 80 | # num: 4 81 | # divisors[4]: 82 | # num: 6 83 | # divisors[6]: [1, 6, 2, 3] 84 | # num: 8 85 | # divisors[8]: 86 | # num: 10 87 | # divisors[10]: 88 | # num: 12 89 | # divisors[12]: [1, 12, 2, 6] 90 | # div: 3 91 | # num: 3 92 | # divisors[3]: [1, 3] 93 | # num: 6 94 | # divisors[6]: [1, 6, 2, 3] 95 | # num: 9 96 | # divisors[9]: 97 | # num: 12 98 | # divisors[12]: [1, 12, 2, 6, 3, 4] 99 | # final divisors: {3=>[1, 3], 1=>[1], 2=>[1, 2], 6=>[1, 6, 2, 3], 12=>[1, 12, 2, 6, 3, 4]} 100 | # nondivisors: [3, 5, 4, 3, 1, 0] 101 | -------------------------------------------------------------------------------- /12-1-chocolates_by_numbers.rb: -------------------------------------------------------------------------------- 1 | # O(n + m) 2 | def slow_solution(n, m) 3 | choc = [1] * (n) 4 | eat = 0 5 | eaten = 0 6 | begin 7 | if choc[eat] 8 | choc[eat] = nil 9 | eaten += 1 10 | end 11 | eat += m 12 | eat %= n if eat > n - 1 13 | end until choc[eat].nil? 14 | eaten 15 | end 16 | 17 | # (0 + km) % n == 0 18 | 19 | # m = 4 20 | # n = 10 21 | # k = 5 22 | 23 | # km % n = 0 24 | # km = cn 25 | # k = cn / m 26 | 27 | # This works (100% performance) but is not best way 28 | # O(log(n + m)) 29 | def fast_solution(n, m) 30 | # eaten = c * n / m. Find c that makes (c * n) % m == 0. 31 | c = 0 32 | begin 33 | c += 1 34 | c = m / n if m == 1000000000 35 | end until (c * n) % m == 0 36 | c * n / m 37 | end 38 | 39 | # https://codility.com/demo/results/training2GZVWK-Y5D/ 40 | 41 | def gcd(u, v) 42 | while v > 0 43 | u, v = v, u % v 44 | end 45 | u 46 | end 47 | 48 | def lcm(u, v) 49 | u * v / gcd(u, v) 50 | end 51 | 52 | # O(log(n + m)) 53 | def fast_solution(n, m) 54 | # n and m meet at their Least Common Multiple 55 | # Dividing this LCM / m = # of steps (chocolates) that are eaten 56 | lcm(n, m) / m 57 | end 58 | -------------------------------------------------------------------------------- /12-2-common_prime_divisors.rb: -------------------------------------------------------------------------------- 1 | def gcd(u, v) 2 | while v > 0 3 | u, v = v, u % v 4 | end 5 | u 6 | end 7 | 8 | def prime_factors(n) 9 | f = 2 10 | factors = [] 11 | while f * f <= n 12 | if n % f == 0 13 | factors << f 14 | n /= f 15 | else 16 | f += 1 17 | end 18 | end 19 | factors << n 20 | end 21 | 22 | # O(Z * (max(A) + max(B))**(1/2)) 23 | # 100% correct, 66% performance 24 | def slow_solution(a, b) 25 | count = 0 26 | a.size.times do |i| 27 | if prime_factors(a[i]).uniq == prime_factors(b[i]).uniq 28 | count += 1 29 | end 30 | end 31 | count 32 | end 33 | 34 | def remove_common_prime_factors(x, y) 35 | # Remove all prime factors of x which also in y. 36 | # Return extra prime factors of x. 37 | while x != 1 38 | gcd = gcd(x, y) 39 | break if gcd == 1 # x has no more common prime factors 40 | x /= gcd # gcd includes all common prime factors of x, y 41 | end 42 | x 43 | end 44 | 45 | # https://codility.com/demo/results/trainingBUKR3A-FHE/ 46 | # O(Z * log(max(A) + max(B))**2) 47 | def fast_solution(a, b) 48 | count = 0 49 | a.zip(b).each do |anum, bnum| 50 | gcd = gcd(anum, bnum) # gcd includes ALL common prime factors 51 | extra_pf = remove_common_prime_factors(anum, gcd) 52 | next if extra_pf != 1 # prime factors of anum, bnum aren't all common 53 | extra_pf = remove_common_prime_factors(bnum, gcd) 54 | count += 1 if extra_pf == 1 55 | end 56 | count 57 | end 58 | 59 | p fast_solution([15, 10, 3], [75, 30, 5]) 60 | -------------------------------------------------------------------------------- /13-1-ladder.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingJGVJ86-CE8/ 2 | 3 | a = [4, 4, 5, 5, 1] 4 | b = [3, 2, 4, 3, 1] 5 | 6 | # returns Fibonacci sequence: 7 | # ways(1) = 1, ways(2) = 2, ways(3) = 3, ways(4) = 5, ways(5) = 8, etc. 8 | def ways(n, c = 0) 9 | if n == 0 10 | c += 1 11 | return c 12 | end 13 | return 0 if n < 0 14 | ways(n - 1, c) + ways(n - 2, c) 15 | end 16 | 17 | def slow_solution(a, b) 18 | ans = [] 19 | w = [] 20 | a.size.times do |i| 21 | unless w[a[i]] 22 | w[a[i]] = ways(a[i]) 23 | end 24 | ans << w[a[i]] % 2**b[i] 25 | end 26 | ans 27 | end 28 | 29 | # O(L), L = size of a, b arrays 30 | def fast_solution(a, b) 31 | ans = [] 32 | ways = [1, 1] 33 | (2..a.max).each do |n| 34 | # ways to climb ladder is just Fibonacci sequence! 35 | ways[n] = ways[n - 1] + ways[n - 2] 36 | end 37 | 38 | a.size.times do |i| 39 | 40 | # If d = power of 2, d & (d - 1) = 0 41 | # x % d = (x & (d − 1)), where & is bitwise AND. 42 | # Also, x / d = x >> power, where 2^power = d 43 | # http://stackoverflow.com/questions/6670715/mod-of-power-2-on-bitwise-operators/6670853#6670853 44 | # http://blog.teamleadnet.com/2012/07/faster-division-and-modulo-operation.html 45 | ans << (ways[a[i]] & (2**b[i] - 1)) 46 | end 47 | ans 48 | end 49 | 50 | start = Time.now 51 | p slow_solution(a, b) 52 | puts "#{Time.now - start} secs" # 6.6e-05 53 | 54 | start = Time.now 55 | p fast_solution(a, b) 56 | puts "#{Time.now - start} secs" # 3.0e-05 -------------------------------------------------------------------------------- /13-2-frog.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/training6524VV-MAF/ 2 | # what's size of smallest subset of array of Fibonacci nums 3 | # that sums to n + 1 (other side of bank) 4 | # and are actual leaves in river (in a)? 5 | 6 | require 'rspec/autorun' 7 | 8 | # http://codility-lessons.blogspot.com/2015/03/lesson-11-fibfrog-fib-frog.html 9 | def solution(a) 10 | n = a.size 11 | 12 | # for n = 0-2, the distance to jump from `-1' (the origin) 13 | # can be 1-3---all Fibonacci nums 14 | return 1 if n <= 2 15 | 16 | # Min jumps to reach this position: 17 | # Cells 0 and 1 can be reached when leaves are there. 18 | # Since they can be reached in first jump, we just care if they have leaves or not 19 | reachable = [a[0], a[1]] + [0] * (n - 1) 20 | 21 | # Since n <= 100,000 and fib(25) == 75025, fib(26) = 121393, 22 | # we only have to generate 25 Fibonacci numbers. 23 | fib = [0, 1] 24 | 25 | (2..25).each do |i| 26 | fib[i] = fib[i - 1] + fib[i - 2] 27 | return 1 if fib[i] == n + 1 # if 1 jump reaches other side, we're done 28 | 29 | # mark positions reachable by 1st jump from shore 30 | aindex = fib[i] - 1 31 | if fib[i] < n + 1 && a[aindex] == 1 32 | reachable[aindex] = 1 33 | end 34 | end 35 | 36 | min_jumps = n + 2 # set it too high to start 37 | 38 | n.times do |i| 39 | next if reachable[i] == 0 #ignore if leaf not reachable 40 | min_jumps_to_i = reachable[i] 41 | 42 | # Make new jump from reachable leaf, looping through all Fib nums 43 | (2..fib.size - 1).each do |j| 44 | next_pos = i + fib[j] 45 | jumps_to_next_pos = min_jumps_to_i + 1 46 | 47 | if next_pos == n # crossed river! 48 | if jumps_to_next_pos < min_jumps 49 | min_jumps = jumps_to_next_pos 50 | break 51 | end 52 | end 53 | 54 | # next jump too big, or no leaf there 55 | next if next_pos > n || a[next_pos] == 0 56 | 57 | # if we've never reached next position before, 58 | # or we can reach next pos with fewer jumps, 59 | # update min number of jumps at that pos. 60 | if reachable[next_pos] == 0 || jumps_to_next_pos < reachable[next_pos] 61 | reachable[next_pos] = jumps_to_next_pos 62 | end 63 | end 64 | end 65 | # reachable: [0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0] for initial example 66 | 67 | # if min_jumps never changed, return -1 68 | min_jumps == n + 2 ? -1 : min_jumps 69 | end 70 | 71 | describe 'Frog' do 72 | it '#solution' do 73 | expect(solution([0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0])).to eq 3 74 | expect(solution([])).to eq 1 75 | expect(solution([1, 1, 1])).to eq 2 76 | expect(solution([1, 1])).to eq 1 77 | end 78 | end -------------------------------------------------------------------------------- /14-1-nailing_planks.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingYEHCRM-PS6/ 2 | # Python: https://codility.com/demo/results/trainingA48TY2-D8F/ 3 | 4 | # Uses "prefix sum" trick from 11-1 Count Semiprimes: 5 | # https://github.com/rayning0/codility/blob/master/11-1-count_semiprimes.rb 6 | 7 | # You are given two non-empty zero-indexed arrays A and B consisting of N integers. 8 | # These arrays represent N planks. A[K] is the start and B[K] the end of the K−th plank. 9 | 10 | # Array C consisting of M integers, represents M nails. 11 | # C[I] is the position where you can hammer in the I−th nail. 12 | # A plank (A[K], B[K]) is nailed if there exists a nail C[I] such that A[K] ≤ C[I] ≤ B[K]. 13 | 14 | # Find the min number of nails J that must be used until all the planks nailed. 15 | # For every plank (A[K], B[K]) such that 0 ≤ K < N, there should exist a 16 | # nail C[I] such that I < J and A[K] ≤ C[I] ≤ B[K]. 17 | 18 | # 4 planks: [1, 4], [4, 5], [5, 9] and [8, 10] 19 | a = [1, 4, 5, 8] 20 | b = [4, 5, 9, 10] 21 | c = [4, 6, 7, 10, 2] 22 | 23 | # if we use the following nails: 24 | 25 | # 0, then planks [1, 4] and [4, 5] will both be nailed. 26 | # 0, 1, then planks [1, 4], [4, 5] and [5, 9] will be nailed. 27 | # 0, 1, 2, then planks [1, 4], [4, 5] and [5, 9] will be nailed. 28 | # 0, 1, 2, 3, then all the planks will be nailed. 29 | # Thus, 4 is the min number of nails that, used sequentially, let all planks be nailed. 30 | 31 | # Can we nail all planks down w/ given # of nails? 32 | def all_nailed?(a, b, c, nails) 33 | planks = 0 34 | # N and M are integers within the range [1..30,000]; 35 | # each element of arrays A, B, C is an integer within the range [1..2*M]; 36 | nails_marked = [0] * (2 * c.size + 1) 37 | 38 | # mark each nail # with a 1 39 | nails.times do |i| 40 | nails_marked[c[i]] = 1 41 | end 42 | 43 | # nails: 4 44 | # nails_marked: [0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1] 45 | 46 | # prefix sums 47 | (1..nails_marked.size - 1).each do |i| 48 | nails_marked[i] += nails_marked[i - 1] 49 | end 50 | 51 | # shows how many nails have been hammered in by certain position 52 | # nails_marked: [0, 0, 0, 0, 1, 1, 2, 3, 3, 3, 4] 53 | 54 | a.size.times do |i| 55 | # prefix sum trick: # of nails hammered in between both ends of plank i 56 | if nails_marked[b[i]] - nails_marked[a[i] - 1] > 0 57 | planks += 1 58 | end 59 | end 60 | planks == a.size # if # of planks nailed = all planks, return TRUE 61 | end 62 | 63 | def solution(a, b, c) 64 | min_nails = 1 65 | max_nails = c.size 66 | nails = -1 67 | # binary search: find min # of nails used to get all planks nailed 68 | while min_nails <= max_nails 69 | mid_nails = (min_nails + max_nails) / 2 70 | if all_nailed?(a, b, c, mid_nails) 71 | max_nails = mid_nails - 1 72 | nails = mid_nails 73 | else 74 | min_nails = mid_nails + 1 75 | end 76 | end 77 | nails 78 | end 79 | 80 | # mid_nails: 3 81 | # nails in position: [0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0] 82 | # prefix sum: [0, 0, 0, 0, 1, 1, 2, 3, 3, 3, 3] 83 | # planks nailed: 3, all planks: 4 84 | # mid_nails: 4 85 | # nails in position: [0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1] 86 | # prefix sum: [0, 0, 0, 0, 1, 1, 2, 3, 3, 3, 4] 87 | # planks nailed: 4, all planks: 4 88 | # 4 89 | 90 | p solution(a, b, c) -------------------------------------------------------------------------------- /14-2-min_max_division.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingB3HKU8-XH6/ 2 | 3 | a = [2, 1, 5, 1, 2, 2, 2] 4 | k = 3 5 | m = 5 6 | 7 | # can block_sum fit k blocks? 8 | def block_sum_ok?(k, a, block_sum) 9 | puts "k: #{k}, block_sum: #{block_sum}" 10 | bsum = 0 11 | blocks = 1 12 | a.each do |elem| 13 | if bsum + elem > block_sum 14 | bsum = elem 15 | blocks += 1 16 | else 17 | bsum += elem 18 | end 19 | puts "elem: #{elem}, blocks: #{blocks}, bsum: #{bsum}" 20 | return false if blocks > k 21 | end 22 | true 23 | end 24 | 25 | # ignore m. it's useless. 26 | # O(N*log(N+M)) 27 | def solution(k, m, a) 28 | min_bsum = a.max 29 | max_bsum = a.inject(&:+) 30 | return max_bsum if k == 1 31 | return min_bsum if k >= a.size 32 | 33 | # binary search on min_sum, given k blocks 34 | while min_bsum <= max_bsum 35 | mid_bsum = (min_bsum + max_bsum) / 2 36 | puts "mid: #{mid_bsum}, min: #{min_bsum}, max: #{max_bsum}" 37 | if block_sum_ok?(k, a, mid_bsum) 38 | max_bsum = mid_bsum - 1 39 | result = mid_bsum 40 | else 41 | min_bsum = mid_bsum + 1 42 | end 43 | end 44 | result 45 | end 46 | 47 | p solution(k, m, a) 48 | 49 | # mid: 10, min: 5, max: 15 50 | # k: 3, block_sum: 10 51 | # elem: 2, blocks: 1, bsum: 2 52 | # elem: 1, blocks: 1, bsum: 3 53 | # elem: 5, blocks: 1, bsum: 8 54 | # elem: 1, blocks: 1, bsum: 9 55 | # elem: 2, blocks: 2, bsum: 2 56 | # elem: 2, blocks: 2, bsum: 4 57 | # elem: 2, blocks: 2, bsum: 6 58 | # mid: 7, min: 5, max: 9 59 | # k: 3, block_sum: 7 60 | # elem: 2, blocks: 1, bsum: 2 61 | # elem: 1, blocks: 1, bsum: 3 62 | # elem: 5, blocks: 2, bsum: 5 63 | # elem: 1, blocks: 2, bsum: 6 64 | # elem: 2, blocks: 3, bsum: 2 65 | # elem: 2, blocks: 3, bsum: 4 66 | # elem: 2, blocks: 3, bsum: 6 67 | # mid: 5, min: 5, max: 6 68 | # k: 3, block_sum: 5 69 | # elem: 2, blocks: 1, bsum: 2 70 | # elem: 1, blocks: 1, bsum: 3 71 | # elem: 5, blocks: 2, bsum: 5 72 | # elem: 1, blocks: 3, bsum: 1 73 | # elem: 2, blocks: 3, bsum: 3 74 | # elem: 2, blocks: 3, bsum: 5 75 | # elem: 2, blocks: 4, bsum: 2 76 | # mid: 6, min: 6, max: 6 77 | # k: 3, block_sum: 6 78 | # elem: 2, blocks: 1, bsum: 2 79 | # elem: 1, blocks: 1, bsum: 3 80 | # elem: 5, blocks: 2, bsum: 5 81 | # elem: 1, blocks: 2, bsum: 6 82 | # elem: 2, blocks: 3, bsum: 2 83 | # elem: 2, blocks: 3, bsum: 4 84 | # elem: 2, blocks: 3, bsum: 6 85 | # 6 -------------------------------------------------------------------------------- /15-1-abs_distinct.rb: -------------------------------------------------------------------------------- 1 | 2 | # https://codility.com/demo/results/trainingP4UTRR-JAT/ 3 | # Fastest: solution4, Caterpillar method! 4 | 5 | # Array is sorted in increasing order. Find number of 6 | # distinct absolute values in array 7 | a = [-5, -4, -4, -2, 0, 1, 1, 1, 4, 5, 6] 8 | #1. Use Array methods 9 | # O(n) or O(n log n) 10 | def solution1(a) 11 | a.map(&:abs).uniq.size 12 | end 13 | 14 | #2. Use Set 15 | # O(n) or O(n log n) 16 | require 'set' 17 | 18 | def solution2(a) 19 | Set.new(a.map(&:abs)).size 20 | end 21 | 22 | #3. Use Hash 23 | # O(n) or O(n log n) 24 | def solution3(a) 25 | freq = {} 26 | a.each do |num| 27 | num = num.abs 28 | freq[num] ? next : freq[num] = 1 29 | end 30 | freq.size 31 | end 32 | 33 | #4. Caterpillar method 34 | 35 | # We prepare 2 cursors. First ('l') scans array from L to R 36 | # and the other ('r') scans from the R to L. 37 | 38 | # If abs(A[l]) > abs(A[r]), we move the left cursor one more step. 39 | # If abs(A[l]) < abs(A[r]), we move the right cursor one more step. 40 | # If abs(A[l]) == abs(A[r]), we move both. 41 | 42 | # In any case of the above, we increment the counter, 43 | # since we found an absolute distinct value. 44 | 45 | # In all cases, if next step is same character as last one, keep moving cursor. 46 | 47 | #a = [-5, -4, -4, -2, 0, 1, 1, 1, 4, 5, 6] 48 | # O(N) or O(N*log(N)) 49 | def solution4(a) 50 | left = 0 51 | right = a.size - 1 52 | distinct = 0 53 | 54 | while left <= right 55 | # puts "left: #{left}, right: #{right}" 56 | # puts "a[l]: #{a[left]}, a[r]: #{a[right]}" 57 | # puts "distinct: #{distinct}" 58 | if a[left].abs > a[right].abs 59 | begin 60 | left += 1 61 | end until a[left] != a[left - 1] 62 | elsif a[left].abs < a[right].abs 63 | begin 64 | right -= 1 65 | end until a[right] != a[right + 1] 66 | else 67 | begin 68 | left += 1 69 | end until a[left] != a[left - 1] 70 | begin 71 | right -= 1 72 | end until a[right] != a[right + 1] 73 | end 74 | 75 | distinct += 1 76 | end 77 | distinct 78 | end 79 | 80 | # left: 0, right: 10 81 | # a[l]: -5, a[r]: 6 82 | # distinct: 0 83 | # left: 0, right: 9 84 | # a[l]: -5, a[r]: 5 85 | # distinct: 1 86 | # left: 1, right: 8 87 | # a[l]: -4, a[r]: 4 88 | # distinct: 2 89 | # left: 3, right: 7 90 | # a[l]: -2, a[r]: 1 91 | # distinct: 3 92 | # left: 4, right: 7 93 | # a[l]: 0, a[r]: 1 94 | # distinct: 4 95 | # left: 4, right: 4 96 | # a[l]: 0, a[r]: 0 97 | # distinct: 5 98 | # 6 99 | 100 | start = Time.now 101 | p solution1(a) 102 | puts "#{Time.now - start} secs" 103 | start = Time.now 104 | p solution2(a) 105 | puts "#{Time.now - start} secs" 106 | start = Time.now 107 | p solution3(a) 108 | puts "#{Time.now - start} secs" 109 | start = Time.now 110 | p solution4(a) 111 | puts "#{Time.now - start} secs" 112 | 113 | # Fastest: solution4, Caterpillar method! 114 | # 1) 4.8e-05 secs 115 | # 2) 3.4e-05 secs 116 | # 3) 1.6e-05 secs 117 | # 4) 1.5e-05 secs 118 | -------------------------------------------------------------------------------- /15-2-count_triangles.rb: -------------------------------------------------------------------------------- 1 | # O(n * n) 2 | def solution(a) 3 | a.sort! 4 | triangles = 0 5 | n = a.size 6 | n.times do |x| 7 | z = x + 2 8 | (x + 1..n - 1).each do |y| 9 | puts "x: #{x}, y: #{y}" 10 | while z < n && a[x] + a[y] > a[z] 11 | puts "#{a[x]} + #{a[y]} > #{a[z]}" 12 | puts "z: #{z}" 13 | z += 1 14 | end 15 | triangles += z - y - 1 16 | puts "triangles: #{triangles}" 17 | end 18 | end 19 | triangles 20 | end 21 | 22 | # In the line :— 23 | # triangles += z - y – 1 24 | 25 | # x is the first item for the triangles. 26 | # y is the the second item for the triangles. 27 | # All the elements between y (exclusive) and z (exclusive) could be the third item for the triangles. 28 | 29 | # z – y – 1 is the number of elements in the range (y, z), both excusive. -------------------------------------------------------------------------------- /15-3-count_distinct_slices.rb: -------------------------------------------------------------------------------- 1 | # codility.com/demo/results/trainingAXJ5N7-8T6/ 2 | # Python (thanks Alex Wice!): codility.com/demo/results/training4J6PQV-A4N/ 3 | 4 | # O(N * (N + M)) 5 | # 100% correct, 40% performance 6 | def slow_solution(m, a) 7 | n = a.size 8 | return 1 if n == 1 9 | distinct = 0 10 | n.times do |back| 11 | (back..n - 1).each do |front| 12 | if a[back..front] == a[back..front].uniq 13 | distinct += 1 14 | else 15 | break 16 | end 17 | end 18 | end 19 | distinct 20 | end 21 | 22 | a = [3, 4, 5, 5, 2] 23 | m = 6 24 | 25 | # For each value of "front", "back" is the farthest left we can 26 | # be where values array[back..front] are all UNIQUE. 27 | # This adds front - back + 1 to # of distinct slices, 28 | # since (back, back), (back, back+1),...(back, front) are all unique slices' 29 | # Our main loop counts the number of unique arrays ending in "front" 30 | 31 | a = [3, 4, 5, 5, 2] 32 | m = 6 33 | MAX_SLICES = 1e9.to_i 34 | 35 | # O(N): both cursors (back, front) only move FORWARD. Never backward. 36 | def fast_solution(m, a) 37 | multiples = 0 # number of #'s in freq that occur >= 2 times 38 | 39 | back = distinct = 0 40 | freq = Hash.new(0) 41 | a.each_with_index do |elem, front| 42 | puts "back: #{back}, front: #{front}, elem: #{elem}, a[#{back}..#{front}]: #{a[back..front]}" 43 | 44 | freq[elem] += 1 45 | multiples += 1 if freq[elem] > 1 46 | 47 | puts "freq: #{freq}, multiples: #{multiples}" 48 | while multiples > 0 49 | # Move back index forward 1 step. 50 | # Have we gotten rid of the element that gave us multiple > 1? 51 | # Does our (back, front) slice still have multiples? 52 | freq[a[back]] -= 1 53 | multiples -= 1 if freq[a[back]] == 1 54 | back += 1 55 | 56 | puts "freq: #{freq}, multiples: #{multiples}, back: #{back}, front: #{front}, a[#{back}..#{front}]: #{a[back..front]}" 57 | end 58 | distinct += front - back + 1 59 | puts "distinct: #{distinct}" 60 | end 61 | [distinct, MAX_SLICES].min 62 | end 63 | 64 | p fast_solution(m, a) 65 | 66 | # back: 0, front: 0, elem: 3, a[0..0]: [3] 67 | # freq: {3=>1}, multiples: 0 68 | # distinct: 1. Slices: (0, 0) 69 | 70 | # back: 0, front: 1, elem: 4, a[0..1]: [3, 4] 71 | # freq: {3=>1, 4=>1}, multiples: 0 72 | # distinct: 3. Slices: (0, 0), (0, 1), (1, 1) 73 | 74 | # back: 0, front: 2, elem: 5, a[0..2]: [3, 4, 5] 75 | # freq: {3=>1, 4=>1, 5=>1}, multiples: 0 76 | # distinct: 6. Slices: (0, 0), (0, 1), (1, 1), (0, 2), (1, 2), (2, 2) 77 | 78 | # back: 0, front: 3, elem: 5, a[0..3]: [3, 4, 5, 5] 79 | # freq: {3=>1, 4=>1, 5=>2}, multiples: 1 80 | # freq: {3=>0, 4=>1, 5=>2}, multiples: 1, back: 1, front: 3, a[1..3]: [4, 5, 5] 81 | # freq: {3=>0, 4=>0, 5=>2}, multiples: 1, back: 2, front: 3, a[2..3]: [5, 5] 82 | # freq: {3=>0, 4=>0, 5=>1}, multiples: 0, back: 3, front: 3, a[3..3]: [5] 83 | # distinct: 7. Slices: (0, 0), (0, 1), (1, 1), (0, 2), (1, 2), (2, 2), (3, 3) 84 | 85 | # back: 3, front: 4, elem: 2, a[3..4]: [5, 2] 86 | # freq: {3=>0, 4=>0, 5=>1, 2=>1}, multiples: 0 87 | # distinct: 9. Slices: (0, 0), (0, 1), (1, 1), (0, 2), (1, 2), (2, 2), (3, 3), (3, 4), (4, 4) 88 | # 9 89 | -------------------------------------------------------------------------------- /16-1-max_nonoverlapping_segments.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingHJERD9-VU2/ 2 | require 'rspec/autorun' 3 | 4 | def solution(a, b) 5 | len = a.size 6 | return len if len <= 1 7 | count = 1 8 | right_end = b[0] 9 | (1..len - 1).each do |i| 10 | next if a[i] <= right_end 11 | count += 1 12 | right_end = b[i] 13 | end 14 | count 15 | end 16 | 17 | describe 'Max Nonoverlapping Segments' do 18 | it 'finds size of non-overlapping set w/ max number of segments' do 19 | a, b = [], [] 20 | expect(solution(a, b)).to eq 0 21 | a, b = [2], [5] 22 | expect(solution(a, b)).to eq 1 23 | a, b = [2, 4], [5, 10] 24 | expect(solution(a, b)).to eq 1 25 | a, b = [2, 5], [5, 10] 26 | expect(solution(a, b)).to eq 1 27 | a, b = [2, 5], [4, 10] 28 | expect(solution(a, b)).to eq 2 29 | a = [1, 3, 7, 9, 9] 30 | b = [5, 6, 8, 9, 10] 31 | expect(solution(a, b)).to eq 3 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /16-2-tie_ropes.rb: -------------------------------------------------------------------------------- 1 | # Greedy Algorithm 2 | #https://codility.com/demo/results/demo93VRFP-B3X/ 3 | 4 | # O(n) 5 | def solution(k, a) 6 | total = 0 7 | ropes = 0 8 | a.each do |rope_length| 9 | total += rope_length 10 | if total >= k 11 | ropes += 1 12 | total = 0 13 | end 14 | end 15 | ropes 16 | end 17 | -------------------------------------------------------------------------------- /17-1-number_solitaire.rb: -------------------------------------------------------------------------------- 1 | # Q: https://app.codility.com/programmers/lessons/17-dynamic_programming/number_solitaire/ 2 | # A: https://app.codility.com/demo/results/training7SDE4H-J5G/ 3 | 4 | a = [1, -2, 0, 9, -1, -2] 5 | a2 = [0, -1, -2, -3, -4, -5, -6, -7] # length > 7 6 | 7 | # http://www.cs.uwm.edu/classes/cs315/Bacon/Lecture/HTML/ch04s13.html 8 | # http://nizhu.github.io/ruby-intro-site/blog/2013/07/28/numbers/ 9 | MININT = -(2**(0.size * 8 -2) -1) - 1 10 | 11 | # - Goal: move pebble to last element of array 12 | # - Throw 6-sided die to get k. 1 <= k <= 6 13 | # - Move pebble from square i to i + k 14 | # - If i + k off the board, throw die again 15 | 16 | # STRATEGY: 17 | # - Greedy algorithm won't work, since we may have negative values! 18 | # - Dynamic programming. For each move, take max of 2 choices: 19 | # 1. starting value + value at that col 20 | # 2. Last value at that col 21 | 22 | # Time complexity: O(n * n). Space complexity: O(n) 23 | def solution(a) 24 | # [1, -INFINITY, -INFINITY, -INFINITY, -INFINITY, -INFINITY] 25 | # [1,-1, 1, 10, 0, -1] row = 1. start_val = 1 26 | # [1,-1, 1, 10, 0, -1] row = 2. start_val = -1 27 | # [1,-1, 1, 10, 0, -1] row = 3. start_val = 1 28 | # [1,-1, 1, 10, 9, 8] row = 4. start_val = 10 29 | # [1,-1, 1, 10, 9, 8] row = 5. start_val = 9 30 | 31 | dp = [a[0]] + [MININT] * (a.size - 1) 32 | (1..a.size - 1).each do |row| 33 | start_index = row - 1 34 | start_val = dp[start_index] 35 | 36 | # we may only move forward up to 6 spots 37 | end_col = [a.size - 1, start_index + 6].min 38 | puts "start_val: #{start_val}. Inner loop start_col: #{row} / end_col: #{end_col}" 39 | 40 | (row..end_col).each do |col| 41 | puts "row: #{row}, col: #{col}" 42 | dp[col] = [dp[col], start_val + a[col]].max 43 | end 44 | p dp 45 | puts 46 | end 47 | 48 | "Answer: #{dp.last}" 49 | end 50 | 51 | p solution(a) 52 | puts "------------------------------------" 53 | p solution(a2) 54 | 55 | # OUTPUT: 56 | 57 | # start_val: 1. Inner loop start_col: 1 / end_col: 5 58 | # row: 1, col: 1 59 | # row: 1, col: 2 60 | # row: 1, col: 3 61 | # row: 1, col: 4 62 | # row: 1, col: 5 63 | # [1, -1, 1, 10, 0, -1] 64 | 65 | # start_val: -1. Inner loop start_col: 2 / end_col: 5 66 | # row: 2, col: 2 67 | # row: 2, col: 3 68 | # row: 2, col: 4 69 | # row: 2, col: 5 70 | # [1, -1, 1, 10, 0, -1] 71 | 72 | # start_val: 1. Inner loop start_col: 3 / end_col: 5 73 | # row: 3, col: 3 74 | # row: 3, col: 4 75 | # row: 3, col: 5 76 | # [1, -1, 1, 10, 0, -1] 77 | 78 | # start_val: 10. Inner loop start_col: 4 / end_col: 5 79 | # row: 4, col: 4 80 | # row: 4, col: 5 81 | # [1, -1, 1, 10, 9, 8] 82 | 83 | # start_val: 9. Inner loop start_col: 5 / end_col: 5 84 | # row: 5, col: 5 85 | # [1, -1, 1, 10, 9, 8] 86 | 87 | # "Answer: 8" 88 | # ------------------------------------ 89 | # start_val: 0. Inner loop start_col: 1 / end_col: 6 90 | # row: 1, col: 1 91 | # row: 1, col: 2 92 | # row: 1, col: 3 93 | # row: 1, col: 4 94 | # row: 1, col: 5 95 | # row: 1, col: 6 96 | # [0, -1, -2, -3, -4, -5, -6, -4611686018427387904] 97 | 98 | # start_val: -1. Inner loop start_col: 2 / end_col: 7 99 | # row: 2, col: 2 100 | # row: 2, col: 3 101 | # row: 2, col: 4 102 | # row: 2, col: 5 103 | # row: 2, col: 6 104 | # row: 2, col: 7 105 | # [0, -1, -2, -3, -4, -5, -6, -8] 106 | 107 | # start_val: -2. Inner loop start_col: 3 / end_col: 7 108 | # row: 3, col: 3 109 | # row: 3, col: 4 110 | # row: 3, col: 5 111 | # row: 3, col: 6 112 | # row: 3, col: 7 113 | # [0, -1, -2, -3, -4, -5, -6, -8] 114 | 115 | # start_val: -3. Inner loop start_col: 4 / end_col: 7 116 | # row: 4, col: 4 117 | # row: 4, col: 5 118 | # row: 4, col: 6 119 | # row: 4, col: 7 120 | # [0, -1, -2, -3, -4, -5, -6, -8] 121 | 122 | # start_val: -4. Inner loop start_col: 5 / end_col: 7 123 | # row: 5, col: 5 124 | # row: 5, col: 6 125 | # row: 5, col: 7 126 | # [0, -1, -2, -3, -4, -5, -6, -8] 127 | 128 | # start_val: -5. Inner loop start_col: 6 / end_col: 7 129 | # row: 6, col: 6 130 | # row: 6, col: 7 131 | # [0, -1, -2, -3, -4, -5, -6, -8] 132 | 133 | # start_val: -6. Inner loop start_col: 7 / end_col: 7 134 | # row: 7, col: 7 135 | # [0, -1, -2, -3, -4, -5, -6, -8] 136 | 137 | # "Answer: -8" -------------------------------------------------------------------------------- /17-2-min_abs_sum_spec.rb: -------------------------------------------------------------------------------- 1 | # MinAbsSum: https://app.codility.com/programmers/lessons/17-dynamic_programming/min_abs_sum/ 2 | # Solution: https://codility.com/media/train/solution-min-abs-sum.pdf 3 | 4 | # if either 1 or -1 is valid, pick -1 5 | 6 | # [1] -1 7 | # [1, 5] -1 1 8 | 9 | # [1, 5, 2] -1 1 10 | # [1, 5, 2, -2] 11 | 12 | # O(n * n * m), where n = a.size, m = max value of a. Remember maxsum = O(n * m) 13 | def slow_solution(a) 14 | m = 0 15 | a.size.times do |i| 16 | a[i] = a[i].abs 17 | m = [a[i], m].max 18 | end 19 | maxsum = a.sum # sum of absolute val of all nums in array 20 | # maxsum = a.map(&:abs).sum <- Ruby shortcut 21 | 22 | # If dp = 1 at an index, it means some combo of elements in a add up to that index 23 | dp = [0] * (maxsum + 1) 24 | dp[0] = 1 25 | 26 | a.size.times do |j| 27 | maxsum.downto(0).each do |possible_sum| 28 | puts "a[j]: #{a[j]}, possible sum: #{possible_sum}" 29 | if (dp[possible_sum] == 1) and (possible_sum + a[j] <= maxsum) 30 | 31 | # if possible_sum + new element a[j] is possible sum, dp = 1! 32 | dp[possible_sum + a[j]] = 1 33 | puts "Mark #{possible_sum + a[j]} as possible sum in dp" 34 | end 35 | end 36 | puts "row: #{j}, a[j]: #{a[j]}, dp: #{dp}" 37 | puts 38 | end 39 | 40 | min_q_minus_p = maxsum 41 | 42 | # Divide array a into 2 parts, where P = sum of part 1 and Q = sum of part 2, 43 | # P + Q = maxsum, and P <= maxsum / 2 <= Q. 44 | # We want largest possible P to get smallest possible Q-P. 45 | 46 | # loop from 0 to maxsum / 2, covering every possible P, Q division 47 | (maxsum / 2 + 1).times do |possible_half_sum| 48 | # puts "possible_half_sum: #{possible_half_sum}" 49 | if dp[possible_half_sum] == 1 # means P or Q = possible_half_sum 50 | q_minus_p = maxsum - 2 * possible_half_sum 51 | # puts "Q - P: #{q_minus_p}" 52 | min_q_minus_p = [min_q_minus_p, q_minus_p].min 53 | # puts "min Q - P: #{min_q_minus_p}" 54 | end 55 | end 56 | 57 | min_q_minus_p 58 | end 59 | 60 | describe '#slow_solution' do 61 | it 'tests something' do 62 | expect(slow_solution([])).to eq 0 63 | expect(slow_solution([1, 5, 2, -2])).to eq 0 64 | expect(slow_solution([-100, -100, -50, -20, -25])).to eq 5 65 | expect(slow_solution([100, 100, 50, 20, 25])).to eq 5 66 | expect(slow_solution([0, 0, 0])).to eq 0 67 | expect(slow_solution([3, 3, 3, 4, 5])).to eq 0 68 | end 69 | end 70 | 71 | # For a = [1, 5, 2, -2]: 72 | 73 | # a[j]: 1, possible sum: 10 74 | # a[j]: 1, possible sum: 9 75 | # a[j]: 1, possible sum: 8 76 | # a[j]: 1, possible sum: 7 77 | # a[j]: 1, possible sum: 6 78 | # a[j]: 1, possible sum: 5 79 | # a[j]: 1, possible sum: 4 80 | # a[j]: 1, possible sum: 3 81 | # a[j]: 1, possible sum: 2 82 | # a[j]: 1, possible sum: 1 83 | # a[j]: 1, possible sum: 0 84 | # Mark 1 as possible sum in dp 85 | # row: 0, a[j]: 1, dp: [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] 86 | 87 | # a[j]: 5, possible sum: 10 88 | # a[j]: 5, possible sum: 9 89 | # a[j]: 5, possible sum: 8 90 | # a[j]: 5, possible sum: 7 91 | # a[j]: 5, possible sum: 6 92 | # a[j]: 5, possible sum: 5 93 | # a[j]: 5, possible sum: 4 94 | # a[j]: 5, possible sum: 3 95 | # a[j]: 5, possible sum: 2 96 | # a[j]: 5, possible sum: 1 97 | # Mark 6 as possible sum in dp 98 | # a[j]: 5, possible sum: 0 99 | # Mark 5 as possible sum in dp 100 | # row: 1, a[j]: 5, dp: [1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0] 101 | 102 | # a[j]: 2, possible sum: 10 103 | # a[j]: 2, possible sum: 9 104 | # a[j]: 2, possible sum: 8 105 | # a[j]: 2, possible sum: 7 106 | # a[j]: 2, possible sum: 6 107 | # Mark 8 as possible sum in dp 108 | # a[j]: 2, possible sum: 5 109 | # Mark 7 as possible sum in dp 110 | # a[j]: 2, possible sum: 4 111 | # a[j]: 2, possible sum: 3 112 | # a[j]: 2, possible sum: 2 113 | # a[j]: 2, possible sum: 1 114 | # Mark 3 as possible sum in dp 115 | # a[j]: 2, possible sum: 0 116 | # Mark 2 as possible sum in dp 117 | # row: 2, a[j]: 2, dp: [1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0] 118 | 119 | # a[j]: 2, possible sum: 10 120 | # a[j]: 2, possible sum: 9 121 | # a[j]: 2, possible sum: 8 122 | # Mark 10 as possible sum in dp 123 | # a[j]: 2, possible sum: 7 124 | # Mark 9 as possible sum in dp 125 | # a[j]: 2, possible sum: 6 126 | # Mark 8 as possible sum in dp 127 | # a[j]: 2, possible sum: 5 128 | # Mark 7 as possible sum in dp 129 | # a[j]: 2, possible sum: 4 130 | # a[j]: 2, possible sum: 3 131 | # Mark 5 as possible sum in dp 132 | # a[j]: 2, possible sum: 2 133 | # Mark 4 as possible sum in dp 134 | # a[j]: 2, possible sum: 1 135 | # Mark 3 as possible sum in dp 136 | # a[j]: 2, possible sum: 0 137 | # Mark 2 as possible sum in dp 138 | # row: 3, a[j]: 2, dp: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 139 | 140 | # possible_half_sum: 0 141 | # Q - P: 10 142 | # min Q - P: 10 143 | # possible_half_sum: 1 144 | # Q - P: 8 145 | # min Q - P: 8 146 | # possible_half_sum: 2 147 | # Q - P: 6 148 | # min Q - P: 6 149 | # possible_half_sum: 3 150 | # Q - P: 4 151 | # min Q - P: 4 152 | # possible_half_sum: 4 153 | # Q - P: 2 154 | # min Q - P: 2 155 | # possible_half_sum: 5 156 | # Q - P: 0 157 | # min Q - P: 0 158 | #____________________________ 159 | # For a = [3, 3, 3, 4, 5]: 160 | 161 | # a[j]: 3, possible sum: 18 162 | # a[j]: 3, possible sum: 17 163 | # a[j]: 3, possible sum: 16 164 | # a[j]: 3, possible sum: 15 165 | # a[j]: 3, possible sum: 14 166 | # a[j]: 3, possible sum: 13 167 | # a[j]: 3, possible sum: 12 168 | # a[j]: 3, possible sum: 11 169 | # a[j]: 3, possible sum: 10 170 | # a[j]: 3, possible sum: 9 171 | # a[j]: 3, possible sum: 8 172 | # a[j]: 3, possible sum: 7 173 | # a[j]: 3, possible sum: 6 174 | # a[j]: 3, possible sum: 5 175 | # a[j]: 3, possible sum: 4 176 | # a[j]: 3, possible sum: 3 177 | # a[j]: 3, possible sum: 2 178 | # a[j]: 3, possible sum: 1 179 | # a[j]: 3, possible sum: 0 180 | # Mark 3 as possible sum in dp 181 | # row: 0, a[j]: 3, dp: [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 182 | 183 | # a[j]: 3, possible sum: 18 184 | # a[j]: 3, possible sum: 17 185 | # a[j]: 3, possible sum: 16 186 | # a[j]: 3, possible sum: 15 187 | # a[j]: 3, possible sum: 14 188 | # a[j]: 3, possible sum: 13 189 | # a[j]: 3, possible sum: 12 190 | # a[j]: 3, possible sum: 11 191 | # a[j]: 3, possible sum: 10 192 | # a[j]: 3, possible sum: 9 193 | # a[j]: 3, possible sum: 8 194 | # a[j]: 3, possible sum: 7 195 | # a[j]: 3, possible sum: 6 196 | # a[j]: 3, possible sum: 5 197 | # a[j]: 3, possible sum: 4 198 | # a[j]: 3, possible sum: 3 199 | # Mark 6 as possible sum in dp 200 | # a[j]: 3, possible sum: 2 201 | # a[j]: 3, possible sum: 1 202 | # a[j]: 3, possible sum: 0 203 | # Mark 3 as possible sum in dp 204 | # row: 1, a[j]: 3, dp: [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 205 | 206 | # a[j]: 3, possible sum: 18 207 | # a[j]: 3, possible sum: 17 208 | # a[j]: 3, possible sum: 16 209 | # a[j]: 3, possible sum: 15 210 | # a[j]: 3, possible sum: 14 211 | # a[j]: 3, possible sum: 13 212 | # a[j]: 3, possible sum: 12 213 | # a[j]: 3, possible sum: 11 214 | # a[j]: 3, possible sum: 10 215 | # a[j]: 3, possible sum: 9 216 | # a[j]: 3, possible sum: 8 217 | # a[j]: 3, possible sum: 7 218 | # a[j]: 3, possible sum: 6 219 | # Mark 9 as possible sum in dp 220 | # a[j]: 3, possible sum: 5 221 | # a[j]: 3, possible sum: 4 222 | # a[j]: 3, possible sum: 3 223 | # Mark 6 as possible sum in dp 224 | # a[j]: 3, possible sum: 2 225 | # a[j]: 3, possible sum: 1 226 | # a[j]: 3, possible sum: 0 227 | # Mark 3 as possible sum in dp 228 | # row: 2, a[j]: 3, dp: [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] 229 | 230 | # a[j]: 4, possible sum: 18 231 | # a[j]: 4, possible sum: 17 232 | # a[j]: 4, possible sum: 16 233 | # a[j]: 4, possible sum: 15 234 | # a[j]: 4, possible sum: 14 235 | # a[j]: 4, possible sum: 13 236 | # a[j]: 4, possible sum: 12 237 | # a[j]: 4, possible sum: 11 238 | # a[j]: 4, possible sum: 10 239 | # a[j]: 4, possible sum: 9 240 | # Mark 13 as possible sum in dp 241 | # a[j]: 4, possible sum: 8 242 | # a[j]: 4, possible sum: 7 243 | # a[j]: 4, possible sum: 6 244 | # Mark 10 as possible sum in dp 245 | # a[j]: 4, possible sum: 5 246 | # a[j]: 4, possible sum: 4 247 | # a[j]: 4, possible sum: 3 248 | # Mark 7 as possible sum in dp 249 | # a[j]: 4, possible sum: 2 250 | # a[j]: 4, possible sum: 1 251 | # a[j]: 4, possible sum: 0 252 | # Mark 4 as possible sum in dp 253 | # row: 3, a[j]: 4, dp: [1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0] 254 | 255 | # a[j]: 5, possible sum: 18 256 | # a[j]: 5, possible sum: 17 257 | # a[j]: 5, possible sum: 16 258 | # a[j]: 5, possible sum: 15 259 | # a[j]: 5, possible sum: 14 260 | # a[j]: 5, possible sum: 13 261 | # Mark 18 as possible sum in dp 262 | # a[j]: 5, possible sum: 12 263 | # a[j]: 5, possible sum: 11 264 | # a[j]: 5, possible sum: 10 265 | # Mark 15 as possible sum in dp 266 | # a[j]: 5, possible sum: 9 267 | # Mark 14 as possible sum in dp 268 | # a[j]: 5, possible sum: 8 269 | # a[j]: 5, possible sum: 7 270 | # Mark 12 as possible sum in dp 271 | # a[j]: 5, possible sum: 6 272 | # Mark 11 as possible sum in dp 273 | # a[j]: 5, possible sum: 5 274 | # a[j]: 5, possible sum: 4 275 | # Mark 9 as possible sum in dp 276 | # a[j]: 5, possible sum: 3 277 | # Mark 8 as possible sum in dp 278 | # a[j]: 5, possible sum: 2 279 | # a[j]: 5, possible sum: 1 280 | # a[j]: 5, possible sum: 0 281 | # Mark 5 as possible sum in dp 282 | # row: 4, a[j]: 5, dp: [1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1] 283 | 284 | # possible_half_sum: 0 285 | # Q - P: 18 286 | # min Q - P: 18 287 | # possible_half_sum: 1 288 | # possible_half_sum: 2 289 | # possible_half_sum: 3 290 | # Q - P: 12 291 | # min Q - P: 12 292 | # possible_half_sum: 4 293 | # Q - P: 10 294 | # min Q - P: 10 295 | # possible_half_sum: 5 296 | # Q - P: 8 297 | # min Q - P: 8 298 | # possible_half_sum: 6 299 | # Q - P: 6 300 | # min Q - P: 6 301 | # possible_half_sum: 7 302 | # Q - P: 4 303 | # min Q - P: 4 304 | # possible_half_sum: 8 305 | # Q - P: 2 306 | # min Q - P: 2 307 | # possible_half_sum: 9 308 | # Q - P: 0 309 | # min Q - P: 0 310 | -------------------------------------------------------------------------------- /99-1-sql_sum.sql: -------------------------------------------------------------------------------- 1 | SELECT sum(v) from elements -------------------------------------------------------------------------------- /99-2-str_symmetry_point.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingUAWWWW-JMP/ 2 | 3 | def solution(s) 4 | length = s.length 5 | return 0 if length == 1 6 | return -1 if s != s.reverse 7 | # answer is just middle index of a palindrome 8 | length.odd? ? length / 2 : -1 9 | end -------------------------------------------------------------------------------- /99-3-tree_height.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/demo/results/trainingXVMN7F-2KA/ 2 | 3 | class Tree 4 | attr_accessor :x, :l, :r 5 | 6 | def initialize(x, l, r) 7 | @x, @l, @r = x, l, r 8 | end 9 | end 10 | 11 | # Original binary tree: 12 | # (5, (3, (20, None, None), (21, None, None)), (10, (1, None, None), None)) 13 | t20 = Tree.new(20, nil, nil) 14 | t21 = Tree.new(21, nil, nil) 15 | t1 = Tree.new(1, nil, nil) 16 | t3 = Tree.new(3, t20, t21) 17 | t10 = Tree.new(10, t1, nil) 18 | t5 = Tree.new(5, t3, t10) 19 | 20 | def solution(t) 21 | # print "#{t.x} " if t 22 | return 0 if t.nil? || (t.l.nil? && t.r.nil?) 23 | 1 + [solution(t.l), solution(t.r)].max 24 | end 25 | 26 | puts solution(t5) 27 | # Output (visits nodes in this order): 28 | # 5 3 20 21 10 1 29 | # Height: 2 30 | 31 | # Test: (5, (3, (20, None, None), (21, None, (23, (26, None, (27, None, None)), None))), (10, (1, None, (22, (29, None, (30, None, None)), (24, (25, None, (28, None, None)), None))), None)) 32 | t20 = Tree.new(20, nil, nil) 33 | t27 = Tree.new(27, nil, nil) 34 | t26 = Tree.new(26, nil, t27) 35 | t23 = Tree.new(23, t26, nil) 36 | t21 = Tree.new(21, nil, t23) 37 | t3 = Tree.new(3, t20, t21) 38 | t30 = Tree.new(30, nil, nil) 39 | t28 = Tree.new(28, nil, nil) 40 | t25 = Tree.new(25, nil, t28) 41 | t24 = Tree.new(24, t25, nil) 42 | t29 = Tree.new(29, nil, t30) 43 | t22 = Tree.new(22, t29, t24) 44 | t1 = Tree.new(1, nil, t22) 45 | t10 = Tree.new(10, t1, nil) 46 | t5 = Tree.new(5, t3, t10) 47 | 48 | puts solution(t5) 49 | # Output (visits nodes in this order): 50 | # 5 3 20 21 23 26 27 10 1 22 29 30 24 25 28 51 | # Height: 6 52 | 53 | 54 | # Test: (5, (3, (20, None, (22, None, None)), (21, None, None)), (10, (1, None, None), None)) 55 | # Output (visits nodes in this order): 56 | # 5 3 20 22 21 10 1 57 | # Height: 3 58 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rspec' 4 | gem 'pry-byebug' 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | byebug (11.0.1) 5 | coderay (1.1.2) 6 | diff-lcs (1.3) 7 | method_source (0.9.2) 8 | pry (0.12.2) 9 | coderay (~> 1.1.0) 10 | method_source (~> 0.9.0) 11 | pry-byebug (3.7.0) 12 | byebug (~> 11.0) 13 | pry (~> 0.10) 14 | rspec (3.7.0) 15 | rspec-core (~> 3.7.0) 16 | rspec-expectations (~> 3.7.0) 17 | rspec-mocks (~> 3.7.0) 18 | rspec-core (3.7.0) 19 | rspec-support (~> 3.7.0) 20 | rspec-expectations (3.7.0) 21 | diff-lcs (>= 1.2.0, < 2.0) 22 | rspec-support (~> 3.7.0) 23 | rspec-mocks (3.7.0) 24 | diff-lcs (>= 1.2.0, < 2.0) 25 | rspec-support (~> 3.7.0) 26 | rspec-support (3.7.0) 27 | 28 | PLATFORMS 29 | ruby 30 | 31 | DEPENDENCIES 32 | pry-byebug 33 | rspec 34 | 35 | BUNDLED WITH 36 | 1.17.3 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Codility solutions 2 | 3 | Solutions to all [17 lessons](https://codility.com/programmers/lessons) from [Codility](https://codility.com), on data structures and algorithms. Many companies use Codility's timed coding challenges to screen software engineers. 4 | 5 | By Raymond Gan -------------------------------------------------------------------------------- /autocomplete.rb: -------------------------------------------------------------------------------- 1 | # Coding Exercise: Autocomplete 2 | # Given a list of words and a list of user keyboard actions (keystrokes or WAIT), produce an array of suggested words (sorted lexicographically) for every time a WAIT action is encountered. 3 | 4 | # Possible actions are: 5 | # - keystroke (a-zA-Z0-9) 6 | # - WAIT 7 | # - BACKSPACE 8 | 9 | # Example: 10 | # Input: 11 | # **************************** 12 | # wordList = ["cats", "cat", "cap", "cape", "cute", "cuts"] 13 | # actions = ["c", "a", "WAIT", "BACKSPACE", "u", "t", "WAIT"] 14 | 15 | # Expected Output: 16 | # **************************** 17 | # [["cap", "cape", "cat", "cats"], ["cute", "cuts"]] 18 | 19 | # Better answer: use a trie 20 | # https://www.youtube.com/watch?v=zIjfhVPRZCg 21 | class TrieNode 22 | @val = "c" 23 | @children = {c: TrieNode} 24 | @end_of_word = true 25 | end 26 | 27 | # c 28 | # | 29 | # a -- 30 | # | | | 31 | # p t d 32 | 33 | # DFS with stack LIFO 34 | # BFS with queue FIFO 35 | 36 | # ['cap', 'cat'] 37 | 38 | # O(n * m) 39 | def solution(word_list, actions) 40 | string = '' 41 | result = [] 42 | word_list.sort! 43 | 44 | actions.each do |action| 45 | case action 46 | when 'WAIT' 47 | result << word_list.select { |word| word =~ /^#{string}/ } 48 | when 'BACKSPACE' 49 | string = string[0..-2] 50 | else 51 | string += action 52 | end 53 | end 54 | 55 | result 56 | end 57 | 58 | describe '#solution' do 59 | it 'returns suggested words based on what user types' do 60 | word_list = ["cats", "cat", "cap", "cape", "cute", "cuts"] # m 61 | actions = ["c", "a", "WAIT", "BACKSPACE", "u", "t", "WAIT"] # n 62 | expect(solution(word_list, actions)).to eq [["cap", "cape", "cat", "cats"], 63 | ["cute", "cuts"],] 64 | end 65 | end 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /binary_search.rb: -------------------------------------------------------------------------------- 1 | # Binary search. Array must be SORTED. 2 | # O(log n). Iterative way is 30% faster than recursive! 3 | 4 | require 'rspec/autorun' 5 | 6 | # recursive 7 | def bsearch_r(a, val) 8 | i = a.size / 2 9 | #puts "a: #{a}, i: #{i}" 10 | return -1 if a.empty? 11 | return val if a[i] == val 12 | if a[i] < val 13 | bsearch_r(a[i + 1..-1], val) 14 | else 15 | bsearch_r(a[0..i - 1], val) 16 | end 17 | end 18 | 19 | a = [5, 7, 10, 16, 21, 38, 44, 46, 52, 67, 79, 80, 95, 99] 20 | 21 | p bsearch_r(a, 5) 22 | p bsearch_r(a, 99) 23 | start = Time.now 24 | p bsearch_r(a, 100) 25 | puts "Recursive binary search: #{Time.now - start} secs" 26 | 27 | # iterative: 30% faster than recursive! 28 | def bsearch_i(a, val) 29 | min = 0 30 | max = a.size - 1 31 | while min <= max 32 | mid = (min + max) / 2 33 | #puts "mid: #{mid}, min: #{min}, max: #{max}" 34 | return val if a[mid] == val 35 | if a[mid] < val 36 | min = mid + 1 37 | else 38 | max = mid - 1 39 | end 40 | end 41 | -1 42 | end 43 | 44 | p bsearch_i(a, 5) 45 | p bsearch_i(a, 99) 46 | start = Time.now 47 | p bsearch_i(a, 100) 48 | puts "Iterative binary search: #{Time.now - start} secs" 49 | 50 | # a: [5, 7, 10, 16, 21, 38, 44, 46, 52, 67, 79, 80, 95, 99], i: 7 51 | # a: [5, 7, 10, 16, 21, 38, 44], i: 3 52 | # a: [5, 7, 10], i: 1 53 | # a: [5], i: 0 54 | # 5 55 | # a: [5, 7, 10, 16, 21, 38, 44, 46, 52, 67, 79, 80, 95, 99], i: 7 56 | # a: [52, 67, 79, 80, 95, 99], i: 3 57 | # a: [95, 99], i: 1 58 | # 99 59 | # a: [5, 7, 10, 16, 21, 38, 44, 46, 52, 67, 79, 80, 95, 99], i: 7 60 | # a: [52, 67, 79, 80, 95, 99], i: 3 61 | # a: [95, 99], i: 1 62 | # a: [], i: 0 63 | # -1 64 | 65 | # mid: 6, min: 0, max: 13 66 | # mid: 2, min: 0, max: 5 67 | # mid: 0, min: 0, max: 1 68 | # 5 69 | # mid: 6, min: 0, max: 13 70 | # mid: 10, min: 7, max: 13 71 | # mid: 12, min: 11, max: 13 72 | # mid: 13, min: 13, max: 13 73 | # 99 74 | # mid: 6, min: 0, max: 13 75 | # mid: 10, min: 7, max: 13 76 | # mid: 12, min: 11, max: 13 77 | # mid: 13, min: 13, max: 13 78 | # -1 79 | #__________________________________ 80 | # Q: Given n binary values x0, x1, . . . , xn−1, such that xi is either 0 or 1. This array 81 | # represents holes in a roof (1 is a hole). You are also given k boards of same size. 82 | # Choose the optimal (minimal) size of the boards that allows all holes to be covered by boards. 83 | 84 | # Board size comes from binary search. If size s covers all holes, then size > s 85 | # also covers all holes. All size < s won't cover holes. 86 | 87 | # Returns # of boards, for given size, you need to cover all holes. 88 | # Use GREEDY algorithm to add new board only if there's hole not covered by last board 89 | def num_of_boards(a, size) 90 | boards = 0 91 | end_of_board = -1 92 | a.size.times do |i| 93 | if a[i] == 1 && end_of_board < i 94 | boards += 1 95 | end_of_board = i + size - 1 96 | end 97 | end 98 | boards 99 | end 100 | 101 | # O(n log n) 102 | def board_size(a, k) 103 | min_size = 1 104 | max_size = a.size 105 | # binary search: O(log n) 106 | while min_size <= max_size 107 | mid_size = (min_size + max_size) / 2 108 | boards = num_of_boards(a, mid_size) # O(n) 109 | return mid_size if boards == k 110 | #puts "mid_size: #{mid_size}, boards: #{boards}" 111 | if boards < k 112 | max_size = mid_size - 1 # shrinking board size increases # of boards 113 | else 114 | min_size = mid_size + 1 # raising board size decreases # of boards 115 | end 116 | end 117 | -1 118 | end 119 | 120 | describe 'Board Size' do 121 | it "gives board size to cover all holes (1's) in array, given k boards" do 122 | expect(board_size([0, 1, 0, 1, 1, 1, 0, 1], 1)).to eq 7 123 | expect(board_size([0, 1, 0, 1, 1, 1, 0, 1], 2)).to eq 4 124 | expect(board_size([0, 1, 0, 1, 1, 1, 0, 1], 3)).to eq 3 125 | expect(board_size([0, 1, 0, 1, 1, 1, 0, 1], 4)).to eq 2 126 | expect(board_size([0, 1, 0, 1, 1, 1, 0, 1], 5)).to eq 1 127 | expect(board_size([0, 1, 0, 1, 1, 1, 0, 1], 6)).to eq -1 128 | end 129 | end -------------------------------------------------------------------------------- /caterpillar_method.rb: -------------------------------------------------------------------------------- 1 | # Move both front and rear ends forwards. 2 | # Move front forward as far as possible, then rear forward 1 step. 3 | 4 | # O(n) 5 | # Does a have a contiguous subset whose sum = s? 6 | def caterpillar(a, s) 7 | front, sum = 0, 0 8 | n = a.size 9 | n.times do |back| 10 | # inch front of caterpillar as many steps as possible 11 | while front < n && sum + a[front] <= s 12 | sum += a[front] 13 | front += 1 14 | end 15 | if sum == s 16 | puts "back: #{back}, front: #{front}" 17 | return true 18 | end 19 | sum -= a[back] # move back of caterpillar forward 1 step 20 | end 21 | false 22 | end 23 | 24 | a = [6, 2, 7, 4, 1, 3, 6] 25 | p caterpillar(a, 12) 26 | # back: 2, front: 5 27 | # true 28 | 29 | # Given n sticks of lengths 1 <= a0 <= a1...<= an-1 <= 10^9), 30 | # count the number of triangles that can be constructed using these sticks. 31 | # We have to count triplets at indices x < y < z, such that ax + ay > az. 32 | # For every pair x, y we can find largest stick z that can be used to construct the triangle. 33 | # Every stick k, such that y < k <= z, can also be used, because the 34 | # ax + ay > ak will still be true. We can add up all these triangles at once. 35 | 36 | # O(n * n) 37 | def triangles(a) 38 | a.sort! 39 | triangles = 0 40 | n = a.size 41 | n.times do |x| 42 | z = x + 2 43 | (x + 1..n - 1).each do |y| 44 | puts "x: #{x}, y: #{y}" 45 | while z < n && a[x] + a[y] > a[z] 46 | puts "#{a[x]} + #{a[y]} > #{a[z]}" 47 | puts "z: #{z}" 48 | z += 1 49 | end 50 | triangles += z - y - 1 51 | puts "triangles: #{triangles}" 52 | end 53 | end 54 | triangles 55 | end 56 | 57 | a = [10, 2, 5, 1, 8, 12] 58 | p a.sort 59 | p "#{triangles(a)} triangles" 60 | -------------------------------------------------------------------------------- /counting_sort.rb: -------------------------------------------------------------------------------- 1 | # https://codility.com/media/train/4-Sorting.pdf 2 | 3 | a = [12, 15, 8, 4, 12, 4, 3, 12, 2, 8, 15, 12, 3, 10, 6, 2, 12, 0, 3] 4 | # all elements are in set 0..k 5 | 6 | def counting_sort(a, k) 7 | count = Array.new(k + 1, 0) 8 | result = [] 9 | a.each do |num| 10 | count[num] += 1 11 | end 12 | 13 | count.each_with_index do |freq, num| 14 | puts "num: #{num}, freq: #{freq}" 15 | freq.times do 16 | result << num 17 | end 18 | end 19 | result 20 | end 21 | 22 | p counting_sort(a, 15) -------------------------------------------------------------------------------- /dynamic_coin_change.rb: -------------------------------------------------------------------------------- 1 | # Dynamic Programming problems have 2 requirements: 2 | 3 | # 1. Optimal Substructure: an optimal solution can be constructed from optimal solutions of its subproblems. 4 | # 2. Overlapping Subproblems: can be broken down into subproblems which are reused several times or a recursive algorithm for the problem solves the same subproblem over and over, rather than generating new subproblems. 5 | 6 | # http://projecteuler.net/problem=31 7 | # http://www.mathblog.dk/project-euler-31-combinations-english-currency-denominations/ 8 | 9 | # In England the currency is made up of pound, £, and pence, p, and there are eight coins in general circulation: 10 | # 1p, 2p, 5p, 10p, 20p, 50p, £1 (100p) and £2 (200p). 11 | # It is possible to make £2 in the following way: 12 | # 1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p 13 | # 14 | # Q: How many different ways can £2 be made using any number of coins? 15 | 16 | # Best video explanation: https://www.youtube.com/watch?v=jaNZ83Q3QGc 17 | def coin_sum(target) 18 | coins = [1, 2, 5, 10, 20, 50, 100, 200] 19 | ways = Array.new(201, 0) 20 | ways[0] = 1 21 | 22 | coins.each do |coin| 23 | # puts "coin: #{coin}" 24 | (coin..target).each do |sum| 25 | # puts "sum: #{sum}. sum-coin: #{sum-coin}" 26 | ways[sum] += ways[sum - coin] 27 | # puts "ways[#{sum}]: #{ways[sum]}" 28 | end 29 | end 30 | ways[target] 31 | end 32 | 33 | p coin_sum(200) 34 | # 73682 ways 35 | 36 | # https://www.hackerrank.com/challenges/ctci-coin-change/problem 37 | # https://www.youtube.com/watch?v=sn0DWI-JdNA 38 | # Q: How many different ways can 200 be made using any number of coins? 39 | # recursive: 40 | def make_change(coins, money, index) 41 | return 1 if money == 0 42 | return 0 if index >= coins.size 43 | 44 | amount_with_coin = 0 45 | ways = 0 46 | 47 | while amount_with_coin <= money 48 | remaining = money - amount_with_coin 49 | ways += make_change(coins, remaining, index + 1) 50 | amount_with_coin += coins[index] 51 | end 52 | 53 | ways 54 | end 55 | 56 | p make_change([1, 2, 5, 10, 20, 50, 100, 200], 200, 0) 57 | # 73682 ways 58 | 59 | # make 2D dynamic programming array (rows = coins, cols = 0..amount) 60 | # http://algorithms.tutorialhorizon.com/dynamic-programming-coin-change-problem/ 61 | def coin_sum_ways(coins, amount) 62 | solution = Array.new(coins.size + 1) { Array.new(amount + 1, 0) } 63 | 64 | # if amount = 0, we have 1 way (empty set) to make this 65 | (coins.size + 1).times do |row| 66 | solution[row][0] = 1 67 | end 68 | 69 | (1..coins.size).each do |row| 70 | (1..amount).each do |col| 71 | coin = coins[row - 1] 72 | # if coin value is <= sum 73 | if coin <= col 74 | solution[row][col] = solution[row - 1][col] + solution[row][col - coin] 75 | else 76 | # copy val from row above 77 | solution[row][col] = solution[row - 1][col] 78 | end 79 | end 80 | end 81 | 82 | solution[coins.size][amount] 83 | end 84 | 85 | coins = [1,2,3] 86 | amount = 5 87 | 88 | # Solution matrix: 89 | # [[1, 0, 0, 0, 0, 0], 90 | # [1, 1, 1, 1, 1, 1], 91 | # [1, 1, 2, 2, 3, 3], 92 | # [1, 1, 2, 3, 4, 5]] 93 | 94 | # 5 ways 95 | 96 | p coin_sum_ways(coins, amount) 97 | coins = [1, 2, 5, 10, 20, 50, 100, 200] 98 | amount = 200 99 | # 73682 ways 100 | 101 | # ---------------------- Codility -------------------------------------- 102 | # https://codility.com/media/train/15-DynamicProgramming.pdf 103 | # Time and space complexity: O(n*k) 104 | require 'pp' 105 | 106 | # MAXINT = (2**(0.size * 8 -2) -1) 107 | MAXINT = 100 # Or pick any number, like 100, bigger than all numbers below row 0 108 | 109 | def dynamic_coin_changing(coins, amount) 110 | puts "Coins: #{coins}. Amount wanted: #{amount}" 111 | solution = Array.new(coins.size + 1) { Array.new(amount + 1, 0) } 112 | 113 | # if no coins and the amount > 0, there's no solution, so solution[0, j] = MAXINT or infinity 114 | solution[0] = [0] + [MAXINT] * amount 115 | 116 | (1..coins.size).each do |row| 117 | coin = coins[row - 1] 118 | 119 | # if the amount to be paid is smaller than highest denomination 120 | # "coin at current row," ignore this coin and copy solution from previous row: 121 | # (for row > 0 and col < "coin at current row") 122 | (0..coin - 1).each do |col| 123 | solution[row][col] = solution[row - 1][col] 124 | end 125 | 126 | # Ex: For coin value 4, loop from cols 0-3. 127 | puts "\nRow: #{row}. Coin: #{coin}. Loop of cols from 0 to #{coin - 1}:" 128 | pp solution 129 | 130 | # Choose option w/ fewer coins. Either: 131 | # 1. Use highest value coin and a smaller amount to be paid remains: 132 | # solution[row][col - coin] + 1 133 | # 2. Don’t use highest value coin and keep answer from previous row 134 | # solution[row - 1][col] 135 | # (for row > 0 and col >= "coin at current row"). 136 | (coin..amount).each do |col| 137 | solution[row][col] = [solution[row][col - coin] + 1, solution[row - 1][col]].min 138 | end 139 | 140 | # Ex: For coin value 4, loop from cols 4-6 141 | puts "loop of cols from Coin: #{coin} to Amount: #{amount}:" 142 | pp solution 143 | end 144 | 145 | puts 146 | "Min # of coins that sum to amount #{amount}: #{solution[coins.size][amount]}" 147 | end 148 | 149 | coins = [1, 3, 4] 150 | amount = 6 151 | puts dynamic_coin_changing(coins, amount) 152 | 153 | # Coins: [1, 3, 4]. Amount wanted: 6 154 | 155 | # Row: 1. Coin: 1. Loop of cols from 0 to 0: 156 | # [[0, 100, 100, 100, 100, 100, 100], 157 | # [0, 0, 0, 0, 0, 0, 0], 158 | # [0, 0, 0, 0, 0, 0, 0], 159 | # [0, 0, 0, 0, 0, 0, 0]] 160 | # loop of cols from Coin: 1 to Amount: 6: 161 | # [[0, 100, 100, 100, 100, 100, 100], 162 | # [0, 1, 2, 3, 4, 5, 6], 163 | # [0, 0, 0, 0, 0, 0, 0], 164 | # [0, 0, 0, 0, 0, 0, 0]] 165 | 166 | # Row: 2. Coin: 3. Loop of cols from 0 to 2: 167 | # [[0, 100, 100, 100, 100, 100, 100], 168 | # [0, 1, 2, 3, 4, 5, 6], 169 | # [0, 1, 2, 0, 0, 0, 0], 170 | # [0, 0, 0, 0, 0, 0, 0]] 171 | # loop of cols from Coin: 3 to Amount: 6: 172 | # [[0, 100, 100, 100, 100, 100, 100], 173 | # [0, 1, 2, 3, 4, 5, 6], 174 | # [0, 1, 2, 1, 2, 3, 2], 175 | # [0, 0, 0, 0, 0, 0, 0]] 176 | 177 | # Row: 3. Coin: 4. Loop of cols from 0 to 3: 178 | # [[0, 100, 100, 100, 100, 100, 100], 179 | # [0, 1, 2, 3, 4, 5, 6], 180 | # [0, 1, 2, 1, 2, 3, 2], 181 | # [0, 1, 2, 1, 0, 0, 0]] 182 | # loop of cols from Coin: 4 to Amount: 6: 183 | # [[0, 100, 100, 100, 100, 100, 100], 184 | # [0, 1, 2, 3, 4, 5, 6], 185 | # [0, 1, 2, 1, 2, 3, 2], 186 | # [0, 1, 2, 1, 1, 2, 2]] 187 | 188 | # "Min # of coins that sum to amount 6: 2" 189 | 190 | puts 191 | # More efficient solution. We only use previous row to find new row. 192 | # We don’t need to remember all rows. 193 | # Time complexity: O(n*k). Space complexity: O(k). 194 | def dynamic_coin_changing_optimized(coins, amount) 195 | solution = [0] + [MAXINT] * amount 196 | (1..coins.size).each do |row| 197 | coin = coins[row - 1] 198 | 199 | (coin..amount).each do |col| 200 | solution[col] = [solution[col - coin] + 1, solution[col]].min 201 | end 202 | 203 | puts "loop of cols from Coin: #{coin} to Amount: #{amount}:" 204 | p solution 205 | end 206 | 207 | puts 208 | "Min # of coins that sum to amount #{amount}: #{solution[amount]}" 209 | end 210 | puts dynamic_coin_changing_optimized(coins, amount) 211 | 212 | # loop of cols from Coin: 1 to Amount: 6: 213 | # [0, 1, 2, 3, 4, 5, 6] <- matches line 162 214 | # loop of cols from Coin: 3 to Amount: 6: 215 | # [0, 1, 2, 1, 2, 3, 2] <- matches line 174 216 | # loop of cols from Coin: 4 to Amount: 6: 217 | # [0, 1, 2, 1, 1, 2, 2] <- matches line 186 218 | 219 | # Min # of coins that sum to amount 6: 2 220 | 221 | #---------- 222 | # A small frog wants to get from position 0 to k (1 <= k <= 10000). 223 | # The frog can jump over any one of n fixed distances s0,s1,...,sn−1 (1 <= si <= k). 224 | # The goal is to count the number of different ways in which the frog can jump to position k. 225 | # To avoid overflow, it is sufficient to return the result modulo q, where q is a given number. 226 | 227 | # There is exactly one way for the frog to jump to position 0, so dp[0] = 1 228 | # 229 | # The number of ways in which the frog can jump to position j with a final jump of si is dp[j − si]. 230 | # Thus, the number of ways in which the frog can get to position j is increased 231 | # by the number of ways of getting to position j − si, for every jump si. 232 | def frog(s, k, q) 233 | dp = [1] + [0] * k 234 | (1..k).each do |j| 235 | s.size.times do |i| 236 | if s[i] <= j 237 | dp[j] = (dp[j] + dp[j - s[i]]) % q 238 | end 239 | end 240 | end 241 | 242 | dp 243 | end 244 | 245 | p frog([2,3,4], 10, 10) 246 | # output: [1, 0, 1, 1, 2, 2, 4, 5, 8, 1, 7] 247 | # But I don't know what it means. 248 | -------------------------------------------------------------------------------- /equi.rb: -------------------------------------------------------------------------------- 1 | # http://blog.codility.com/2011/03/solutions-for-task-equi.html 2 | # O(n) space/time complexity answer 3 | 4 | # def solution(a) 5 | # return -1 if a.length == 0 6 | # total_sum = a.inject(&:+) 7 | # left_sum = 0 8 | # a.each_with_index do |element, i| 9 | # right_sum = total_sum - element - left_sum 10 | # return i if left_sum == right_sum 11 | # left_sum += element 12 | # end 13 | # -1 14 | # end 15 | 16 | # https://codesays.com/2014/solution-to-equi-by-codility/ 17 | def solution(a) 18 | left = 0 19 | right = a.sum 20 | a.each_with_index do |element, index| 21 | right -= element 22 | return index if left == right 23 | left += element 24 | end 25 | -1 26 | end 27 | 28 | a = [-1, 3, -4, 5, 1, -6, 2, 1] 29 | p solution(a) 30 | -------------------------------------------------------------------------------- /fibonacci.rb: -------------------------------------------------------------------------------- 1 | # http://introcs.cs.princeton.edu/java/23recursion/ (theory) 2 | 3 | # recursive 4 | # O(2^n) 5 | def fib(n, c) 6 | indent = ' ' * c 7 | puts "#{indent}fibonacci(#{n})" 8 | return n if n <= 1 9 | fib(n - 2, c + 2) + fib(n - 1, c + 2) 10 | end 11 | 12 | #p fib(7, 0) 13 | 14 | # Recursion sucks here: O(2^n) 15 | 16 | # fibonacci(7) 17 | # fibonacci(5) 18 | # fibonacci(3) 19 | # fibonacci(1) 20 | # fibonacci(2) 21 | # fibonacci(0) 22 | # fibonacci(1) 23 | # fibonacci(4) 24 | # fibonacci(2) 25 | # fibonacci(0) 26 | # fibonacci(1) 27 | # fibonacci(3) 28 | # fibonacci(1) 29 | # fibonacci(2) 30 | # fibonacci(0) 31 | # fibonacci(1) 32 | # fibonacci(6) 33 | # fibonacci(4) 34 | # fibonacci(2) 35 | # fibonacci(0) 36 | # fibonacci(1) 37 | # fibonacci(3) 38 | # fibonacci(1) 39 | # fibonacci(2) 40 | # fibonacci(0) 41 | # fibonacci(1) 42 | # fibonacci(5) 43 | # fibonacci(3) 44 | # fibonacci(1) 45 | # fibonacci(2) 46 | # fibonacci(0) 47 | # fibonacci(1) 48 | # fibonacci(4) 49 | # fibonacci(2) 50 | # fibonacci(0) 51 | # fibonacci(1) 52 | # fibonacci(3) 53 | # fibonacci(1) 54 | # fibonacci(2) 55 | # fibonacci(0) 56 | # fibonacci(1) 57 | # 13 58 | 59 | # iterative (bottom up) 60 | # O(n) time, O(n) space 61 | def fib(n) 62 | return n if n <= 1 63 | f = [0, 1] 64 | (2..n).each do |i| 65 | f[i] = f[i - 2] + f[i - 1] 66 | end 67 | f[n] 68 | end 69 | 70 | # iterative 71 | # O(n) time, O(1) space 72 | def fib(n) 73 | return n if n <= 1 74 | f0, f1, f2 = 0, 1, 1 75 | (2..n).each do |i| 76 | f2 = f0 + f1 77 | f0 = f1 78 | f1 = f2 79 | end 80 | f2 81 | end 82 | 83 | #p fib(10) # answer 55 84 | 85 | # recursive (top down) 86 | # O(2n) = O(n) 87 | class TopDownFib 88 | def initialize 89 | @f = [0, 1] 90 | end 91 | 92 | def fib(n) 93 | return n if n <= 1 94 | return @f[n] if @f[n] 95 | @f[n] = fib(n - 2) + fib(n - 1) 96 | end 97 | end 98 | 99 | #p TopDownFib.new.fib(15) # answer 89 100 | 101 | # For all given numbers x0, x1,...,xn-1, such that 1 <= xi <= m <= 1,000,000, 102 | # check if they may be presented as sum of two Fibonacci numbers. 103 | # fib(30) = 832040, fib(31) = 1346269 (> 1,000,000) 104 | 105 | # O(n + m) 106 | def check_sum_fib(a) 107 | f = [0, 1] 108 | n = 2 109 | m = a.max 110 | puts "a: #{a}, m: #{m}" 111 | loop do 112 | fib = fib(n) 113 | break if fib > m 114 | f[n] = fib 115 | n += 1 # max value = 30 116 | end 117 | puts "Fib nums: #{f}" 118 | fib_sum = [] 119 | 120 | (0..f.size - 2).each do |i| 121 | (i + 1..f.size - 1).each do |j| 122 | k = f[i] + f[j] 123 | # If some of the Fibonacci #'s sum to k <= m, we mark index k in array to 124 | # denote that k can be presented as sum of two Fibonacci numbers. 125 | fib_sum[k] = [f[i], f[j]] if k <= m 126 | end 127 | end 128 | 129 | # for each number xi we can answer whether it is the sum of two Fibonacci 130 | # numbers in constant time. The total time complexity is O(n + m). 131 | a.each do |num| 132 | if fib_sum[num] 133 | puts "#{num} is sum of #{fib_sum[num]}." 134 | end 135 | end 136 | end 137 | 138 | check_sum_fib([29, 178, 2584, 6368, 12325, 37323, 242786, 346468, 734774, 635622]) 139 | 140 | # Fib nums: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229] 141 | # 29 is sum of [8, 21]. 142 | # 178 is sum of [34, 144]. 143 | # 2584 is sum of [987, 1597]. 144 | # 242786 is sum of [46368, 196418]. 145 | # 346468 is sum of [28657, 317811]. 146 | # 635622 is sum of [121393, 514229]. -------------------------------------------------------------------------------- /gcd.rb: -------------------------------------------------------------------------------- 1 | # Euclidean algorithm for Greatest Common Divisor of 2 positive integers 2 | 3 | # Subtraction method: O(n) 4 | def gcd_subtraction(a, b) 5 | return a if a == b 6 | if a > b 7 | gcd_subtraction(a - b, b) 8 | else 9 | gcd_subtraction(a, b - a) 10 | end 11 | end 12 | 13 | # Division method: O(log(a + b)) 14 | def gcd_division1(a, b) 15 | if a % b == 0 16 | return b 17 | else 18 | gcd_division1(b, a % b) 19 | end 20 | end 21 | 22 | # Or: FASTEST OF ALL 4 ALGORITHMS!!! 23 | def gcd_division2(u, v) 24 | while v > 0 25 | u, v = v, u % v 26 | end 27 | u 28 | end 29 | 30 | # Binary (Stein's) algorithm: 31 | 32 | # If u and v are both even, gcd(u,v) = 2 gcd(u/2, v/2). 33 | # If u is even and v is odd, gcd(u,v) = gcd(u/2, v). 34 | # Otherwise both are odd, and gcd(u,v) = gcd(|u-v|/2, v). (difference of two odd numbers is even.) 35 | 36 | # Time complexity: O(log(a · b)) = O(log a + b) = O(log n). 37 | # For very large integers, O((log n)^2), since each arithmetic operation can be done in O(log n) time. 38 | 39 | # Explains each binary GCD step well: 40 | # http://www.cse.unt.edu/~tarau/teaching/PP/NumberTheoretical/GCD/Binary%20GCD%20algorithm.pdf 41 | # http://www.cut-the-knot.org/blue/binary.shtml 42 | 43 | def gcd_binary(u, v) 44 | return v if u == 0 || u == v 45 | return u if v == 0 46 | 47 | # bitwise operations: 48 | # >> 1 means divide by 2 49 | # << 1 means multiply by 2 50 | if u.even? && v.even? 51 | return gcd_binary(u >> 1, v >> 1) << 1 52 | elsif u.even? 53 | return gcd_binary(u >> 1, v) 54 | elsif v.even? 55 | return gcd_binary(u, v >> 1) 56 | else 57 | return gcd_binary((u - v).abs >> 1, [u, v].min) 58 | end 59 | 60 | # if u.even? && v.even? 61 | # return gcd_binary(u / 2, v / 2) * 2 62 | # elsif u.even? 63 | # return gcd_binary(u / 2, v) 64 | # elsif v.even? 65 | # return gcd_binary(u, v / 2) 66 | # else 67 | # return gcd_binary((u - v).abs / 2, [u, v].min) 68 | # end 69 | end 70 | 71 | # Fastest to Slowest: 72 | gcd_division2(2322, 654) 73 | # 3.0e-06 secs 74 | 75 | gcd_division1(2322, 654) 76 | # 4.0e-06 secs 77 | 78 | gcd_subtraction(2322, 654) 79 | # 5.0e-06 secs 80 | 81 | gcd_binary(2322, 654) 82 | # 2.6e-05 secs 83 | 84 | 85 | # Least Common Multiple (LCM) of a and b = 86 | # smallest positive integer divisible by both a and b 87 | # 88 | # lcm(a, b) = a * b / gcd(a, b) 89 | # 90 | # Intuitive reason: a * b is clearly divisible by both a and b. 91 | # How to get SMALLEST divisible integer? 92 | # Divide by BIGGEST possible integer, the GREATEST COMMON DIVISOR. 93 | 94 | # lcm(a1, a2, ..., an) = lcm(a1, lcm(a2, a3, ... , an)) 95 | 96 | def lcm(*a) # LCM of multiple numbers 97 | if a.size == 2 98 | return a[0] * a[1] / gcd_division2(a[0], a[1]) 99 | end 100 | 101 | lcm(a.shift, lcm(*a)) 102 | end 103 | 104 | p lcm(16, 20, 24, 41) # 9840 -------------------------------------------------------------------------------- /google_interview.rb: -------------------------------------------------------------------------------- 1 | # My versions of the Google interview problem shown here: 2 | # https://www.youtube.com/watch?v=XKu_SEDAykw 3 | 4 | a1 = [1, 2, 3, 9] # SORTED arrays. Could have negatives. 5 | a2 = [1, 2, 4, 4] 6 | 7 | # 1. Slow solution, with binary search 8 | # O(n log n) 9 | def has_pair_with_sum(data, sum) 10 | (0..data.size - 2).each do |i| 11 | num = data[i] 12 | complement = sum - num 13 | puts "num: #{num}, complement: #{complement}" 14 | 15 | # binary search for num's complement in rest of array 16 | lo = i + 1 17 | hi = data.size - 1 18 | while lo <= hi 19 | mid = (lo + hi) / 2 20 | puts "data[mid]: #{data[mid]}, mid: #{mid}, lo: #{lo}, hi: #{hi}" 21 | return true if data[mid] == complement 22 | if data[mid] < complement 23 | lo = mid + 1 24 | else 25 | hi = mid - 1 26 | end 27 | end 28 | end 29 | false 30 | end 31 | 32 | p has_pair_with_sum(a1, 8) # false 33 | p has_pair_with_sum(a2, 8) # true 34 | 35 | # num: 1, complement: 7 36 | # data[mid]: 3, mid: 2, lo: 1, hi: 3 37 | # data[mid]: 9, mid: 3, lo: 3, hi: 3 38 | # num: 2, complement: 6 39 | # data[mid]: 3, mid: 2, lo: 2, hi: 3 40 | # data[mid]: 9, mid: 3, lo: 3, hi: 3 41 | # num: 3, complement: 5 42 | # data[mid]: 9, mid: 3, lo: 3, hi: 3 43 | # false 44 | 45 | # num: 1, complement: 7 46 | # data[mid]: 4, mid: 2, lo: 1, hi: 3 47 | # data[mid]: 4, mid: 3, lo: 3, hi: 3 48 | # num: 2, complement: 6 49 | # data[mid]: 4, mid: 2, lo: 2, hi: 3 50 | # data[mid]: 4, mid: 3, lo: 3, hi: 3 51 | # num: 4, complement: 4 52 | # data[mid]: 4, mid: 3, lo: 3, hi: 3 53 | # true 54 | 55 | # 2. Fast solution, with 2 cursors moving from ends to center 56 | # O(n) 57 | def has_pair_with_sum(data, sum) 58 | lo = 0 59 | hi = data.size - 1 60 | test_sum = data[lo] + data[hi] 61 | while lo <= hi 62 | puts "test_sum: #{test_sum}, data[lo]: #{data[lo]}, data[hi]: #{data[hi]}" 63 | return true if test_sum == sum 64 | if test_sum > sum 65 | hi -= 1 66 | else 67 | lo += 1 68 | end 69 | test_sum = data[lo] + data[hi] 70 | end 71 | false 72 | end 73 | 74 | p has_pair_with_sum(a1, 8) # false 75 | p has_pair_with_sum(a2, 8) # true 76 | 77 | # test_sum: 10, data[lo]: 1, data[hi]: 9 78 | # test_sum: 4, data[lo]: 1, data[hi]: 3 79 | # test_sum: 5, data[lo]: 2, data[hi]: 3 80 | # test_sum: 6, data[lo]: 3, data[hi]: 3 81 | # false 82 | 83 | # test_sum: 5, data[lo]: 1, data[hi]: 4 84 | # test_sum: 6, data[lo]: 2, data[hi]: 4 85 | # test_sum: 8, data[lo]: 4, data[hi]: 4 86 | # true 87 | 88 | a1 = [9, 2, 3, 1] 89 | a2 = [4, -2, 1, 4] # UNSORTED, with negatives 90 | 91 | # 3. Google guy's fast solution, with Set 92 | # http://ruby-doc.org/stdlib-2.4.1/libdoc/set/rdoc/Set.html 93 | # O(n) 94 | require 'set' 95 | def has_pair_with_sum(data, sum) 96 | complement = Set.new [] 97 | data.each do |value| 98 | # Ruby's Set has O(1) constant time, fast lookup speeds, like Hash 99 | return true if complement.include?(value) 100 | complement.add(sum - value) 101 | p complement 102 | end 103 | false 104 | end 105 | 106 | p has_pair_with_sum(a1, 8) # false 107 | p has_pair_with_sum(a2, 8) # true 108 | 109 | # 110 | # 111 | # 112 | # 113 | # false 114 | 115 | # 116 | # 117 | # 118 | # true 119 | 120 | # The YouTube video has a C++ convention: 121 | 122 | # This explains why C++ uses "!= end()" to detect the end of loops, 123 | # plus how to iterate over an "unordered set" (data structure the Google guy chose): 124 | # http://stackoverflow.com/questions/9963401/why-are-standard-iterator-ranges-begin-end-instead-of-begin-end 125 | # http://stackoverflow.com/questions/9087217/how-does-one-iterate-through-an-unordered-set-in-c 126 | 127 | # end() is actually an iterator ONE PAST THE END of complement, not the actual end. 128 | # So "if complement.find(value) != complement.end()" from the video means: 129 | 130 | # "If the complement set has value in it, plus we haven't gone past the end of the set" 131 | 132 | # We obviously don't need this != end() check in Ruby. 133 | 134 | 135 | # 4. Fast solution with hash 136 | # O(n) 137 | def has_pair_with_sum(data, sum) 138 | complement = {} # Hash has O(1) constant lookup time. 139 | data.each do |value| 140 | 141 | # Why is Hash lookup in Ruby so fast? This guy recreates a Hash with a custom class, 142 | # then shows how a Ruby Hash puts items in bins. 143 | 144 | # https://blog.engineyard.com/2013/hash-lookup-in-ruby-why-is-it-so-fast 145 | # Instead of using single array to store all its entries, 146 | # hashes in Ruby use an array of arrays or “bins”. 147 | # First, it calculates a unique integer value for each entry. Here he uses Object#hash. 148 | # Then, Ruby divides this hash integer by the total number of bins and obtains the remainder or modulus. 149 | # This modulus is used as the bin index for that specific entry. 150 | 151 | return true if complement[value] 152 | complement[sum - value] = 1 153 | p complement 154 | end 155 | false 156 | end 157 | 158 | p has_pair_with_sum(a1, 8) # false 159 | p has_pair_with_sum(a2, 8) # true 160 | 161 | # {-1=>1} 162 | # {-1=>1, 6=>1} 163 | # {-1=>1, 6=>1, 5=>1} 164 | # {-1=>1, 6=>1, 5=>1, 7=>1} 165 | # false 166 | 167 | # {4=>1} 168 | # {4=>1, 10=>1} 169 | # {4=>1, 10=>1, 7=>1} 170 | # true 171 | 172 | # 5. Bad solution with array: WON'T work! 173 | # Only works if sum >= value always, impossible for a general integer array 174 | # O(n) 175 | def has_pair_with_sum(data, sum) 176 | complement = [] 177 | data.each do |value| 178 | 179 | # Array has O(1) constant access time, because: 180 | # http://stackoverflow.com/questions/7297916/why-does-accessing-an-element-in-an-array-take-constant-time 181 | 182 | # Arrays are stored in memory sequentially. When you access array[3] you are telling the computer, 183 | # "Get the memory address of the beginning of array, then add 3 to it, then access that spot." 184 | # Since adding takes constant time, so does array access! 185 | 186 | return true if complement[value] 187 | complement[sum - value] = 1 188 | p complement 189 | end 190 | false 191 | end 192 | -------------------------------------------------------------------------------- /greedy_coins_and_canoes.rb: -------------------------------------------------------------------------------- 1 | # Find min # of coins with which a given amount of $ can be paid. Use greedy algorithm (not optimal). Make locally best choice at any given moment, which may not be overall best choice. 2 | 3 | def greedy_coin_changing(coins, amount) 4 | result = [] 5 | (coins.size - 1).downto(0).each do |i| 6 | result << [coins[i], amount / coins[i]] 7 | amount %= coins[i] # amount left after using coins[i] 8 | end 9 | result 10 | end 11 | 12 | coins = [2, 3, 5, 6] 13 | amount = 10 14 | p greedy_coin_changing(coins, amount) 15 | 16 | # Output: [coin value, # of coins] 17 | # [[6, 1], [5, 0], [3, 1], [2, 0]]. Non-optimal answer. 18 | # To return total of <= 10, we use one 6 coin and one 3 coin. 19 | 20 | 21 | # There are n canoeists weighing w0, w1,...wn-1. w is sorted array. 22 | # Seat them in min # of 2-seat canoes w/ max load of max_load. Each guy weighs <= max_load. 23 | def greedy_canoe_a(w, max_load) 24 | skinny, fat = [], [] 25 | 26 | # Divide w into 2 arrays: skinny, fat 27 | w.size.times do |i| 28 | if w[i] + w[-1] <= max_load 29 | skinny << w[i] 30 | else 31 | fat << w[i] 32 | end 33 | end 34 | 35 | # fat << w[-1] <---mistake in Codility 14-GreedyAlgorithms PDF? 36 | canoes = 0 37 | 38 | while skinny.any? || fat.any? 39 | puts "Skinny: #{skinny}, Fat: #{fat}" 40 | # put heaviest skinny guy w/ heaviest fat guy 41 | s = skinny.any? ? skinny.pop : '' 42 | f = fat.pop 43 | puts "Put #{s}, #{f} in 1 canoe" 44 | canoes += 1 45 | 46 | # after enmptying "fat", move heaviest skinny guy back to "fat" 47 | if skinny.any? && fat.empty? 48 | fat << skinny.pop 49 | end 50 | 51 | # if lightest + heaviest fat guys can fit in canoe, 52 | # move lightest fat guy to "skinny," so next loop pairs them 53 | while fat.size > 1 && fat[0] + fat[-1] <= max_load 54 | skinny << fat.shift 55 | end 56 | end 57 | puts "Total canoes: #{canoes}" 58 | canoes 59 | end 60 | 61 | w = [5, 8, 10, 17, 20, 21, 25, 30] 62 | max_load = 50 63 | greedy_canoe_a(w, max_load) 64 | 65 | # Skinny: [5, 8, 10, 17, 20], Fat: [21, 25, 30] 66 | # Put 20, 30 in 1 canoe 67 | # Skinny: [5, 8, 10, 17, 21], Fat: [25] 68 | # Put 21, 25 in 1 canoe 69 | # Skinny: [5, 8, 10], Fat: [17] 70 | # Put 10, 17 in 1 canoe 71 | # Skinny: [5], Fat: [8] 72 | # Put 5, 8 in 1 canoe 73 | # Total canoes: 4 74 | # 75 | w = [5, 8, 10, 17, 20, 21, 25, 30] 76 | max_load = 30 77 | greedy_canoe_a(w, max_load) 78 | # 79 | # Skinny: [], Fat: [5, 8, 10, 17, 20, 21, 25, 30] 80 | # Put , 30 in 1 canoe 81 | # Skinny: [5], Fat: [8, 10, 17, 20, 21, 25] 82 | # Put 5, 25 in 1 canoe 83 | # Skinny: [8], Fat: [10, 17, 20, 21] 84 | # Put 8, 21 in 1 canoe 85 | # Skinny: [10], Fat: [17, 20] 86 | # Put 10, 20 in 1 canoe 87 | # Skinny: [], Fat: [17] 88 | # Put , 17 in 1 canoe 89 | # Total canoes: 5 90 | 91 | # O(n): Sit fattest canoeist with the thinnest, as long as total weight <= max_load. If not, fattest canoeist is seated alone in canoe. 92 | def greedy_canoe_b(w, max_load) 93 | canoes = 0 94 | i, j = 0, w.size - 1 95 | 96 | while i <= j 97 | if w[i] + w[j] <= max_load 98 | puts "Sit #{w[i]}, #{w[j]} in 1 canoe. Total weight #{w[i] + w[j]}" 99 | i += 1 100 | else 101 | puts "Sit #{w[j]} alone in 1 canoe." 102 | end 103 | canoes += 1 104 | j -= 1 105 | end 106 | puts "Total canoes: #{canoes}" 107 | canoes 108 | end 109 | 110 | w = [5, 8, 10, 17, 20, 21, 25, 30] 111 | max_load = 30 112 | 113 | # greedy_canoe_b(w, max_load) 114 | 115 | # Sit 30 alone in 1 canoe. 116 | # Sit 5, 25 in 1 canoe. Total weight 30 117 | # Sit 8, 21 in 1 canoe. Total weight 29 118 | # Sit 10, 20 in 1 canoe. Total weight 30 119 | # Sit 17 alone in 1 canoe. 120 | # Total canoes: 5 121 | -------------------------------------------------------------------------------- /grocery_store.rb: -------------------------------------------------------------------------------- 1 | def grocery_store(a) 2 | zeroes, ones = 0, 0 3 | a.each do |action| 4 | if action == 0 5 | zeroes += 1 6 | else 7 | ones += 1 8 | end 9 | end 10 | if zeroes - ones < 0 11 | (zeroes - ones).abs 12 | else 13 | 0 14 | end 15 | end 16 | 17 | a = [1, 1, 0, 0, 1, 0, 0] 18 | p grocery_store(a) -------------------------------------------------------------------------------- /leader.rb: -------------------------------------------------------------------------------- 1 | 2 | A = [4, 6, 6, 6, 6, 8, 8] 3 | n = 7 4 | size = 5 | value = 6 6 | k = 5 7 | A[k] = 8 8 | 9 | # def hash_leader(n) 10 | # freq = Hash.new(0) 11 | # n.each do |num| 12 | # freq[num] += 1 13 | # end 14 | 15 | # max_freq = freq.values.max 16 | # if max_freq > n.size / 2 17 | # return freq.key(max_freq) 18 | # end 19 | # end 20 | 21 | # O(n*n) 22 | def slow_leader(n) 23 | length = n.size 24 | leader = -1 25 | n.each do |candidate| 26 | count = 0 27 | n.each do |num| 28 | count += 1 if num == candidate 29 | end 30 | leader = candidate if count > length/2 31 | end 32 | leader 33 | end 34 | 35 | # O(n log n) because sort method is O(n log n) 36 | def fast_leader(n) 37 | n.sort! 38 | length = n.size 39 | leader = -1 40 | freq = 0 41 | candidate = n[length / 2] 42 | n.each do |num| 43 | freq += 1 if num == candidate 44 | leader = candidate if freq > length / 2 45 | end 46 | leader 47 | end 48 | 49 | # O(n) 50 | def golden_leader(n) 51 | stack = [] 52 | leader = -1 53 | stack_i = 0 54 | n.each do |num| 55 | stack << num 56 | if stack_i == 0 57 | stack_i += 1 58 | next 59 | end 60 | if stack[stack_i] != stack[stack_i - 1] 61 | stack.pop(2) 62 | stack_i -= 2 63 | end 64 | stack_i += 1 65 | end 66 | candidate = stack.last 67 | 68 | freq = 0 69 | n.each do |num| 70 | freq += 1 if num == candidate 71 | leader = candidate if freq > n.size / 2 72 | end 73 | leader 74 | end 75 | 76 | p golden_leader(n) 77 | 78 | Python solution from PDF: 79 | 80 | def goldenLeader(A): 81 | n = len(A) 82 | size = 0 83 | for k in xrange(n): 84 | if (size == 0): 85 | size += 1 86 | value = A[k] 87 | else: 88 | if (value != A[k]): 89 | size -= 1 90 | else: 91 | size += 1 92 | candidate = -1 93 | if (size > 0): 94 | candidate = value 95 | leader = -1 96 | count = 0 97 | for k in xrange(n): 98 | if (A[k] == candidate): 99 | count += 1 100 | if (count > n // 2): 101 | leader = candidate 102 | return leader -------------------------------------------------------------------------------- /lessons/01-Iterations.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/01-Iterations.pdf -------------------------------------------------------------------------------- /lessons/02-Arrays.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/02-Arrays.pdf -------------------------------------------------------------------------------- /lessons/03-TimeComplexity.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/03-TimeComplexity.pdf -------------------------------------------------------------------------------- /lessons/04-CountingElements.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/04-CountingElements.pdf -------------------------------------------------------------------------------- /lessons/05-PrefixSums.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/05-PrefixSums.pdf -------------------------------------------------------------------------------- /lessons/06-Sorting.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/06-Sorting.pdf -------------------------------------------------------------------------------- /lessons/07-Stacks and Queues.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/07-Stacks and Queues.pdf -------------------------------------------------------------------------------- /lessons/08-Leader.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/08-Leader.pdf -------------------------------------------------------------------------------- /lessons/09-MaxSlice.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/09-MaxSlice.pdf -------------------------------------------------------------------------------- /lessons/10-PrimeNumbers.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/10-PrimeNumbers.pdf -------------------------------------------------------------------------------- /lessons/11-Sieve.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/11-Sieve.pdf -------------------------------------------------------------------------------- /lessons/12-Gcd.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/12-Gcd.pdf -------------------------------------------------------------------------------- /lessons/13-Fibonacci.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/13-Fibonacci.pdf -------------------------------------------------------------------------------- /lessons/14-BinarySearch.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/14-BinarySearch.pdf -------------------------------------------------------------------------------- /lessons/15-CaterpillarMethod.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/15-CaterpillarMethod.pdf -------------------------------------------------------------------------------- /lessons/16-GreedyAlgorithms.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/16-GreedyAlgorithms.pdf -------------------------------------------------------------------------------- /lessons/17-DynamicProgramming.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/lessons/17-DynamicProgramming.pdf -------------------------------------------------------------------------------- /max_slice.rb: -------------------------------------------------------------------------------- 1 | a = [5, -7, 3, 5, -2, 4, -1] 2 | 3 | def prefix_sum(a) 4 | p = [0] 5 | (1..a.size).each do |k| 6 | p[k] = p[k - 1] + a[k - 1] 7 | end 8 | p 9 | end 10 | 11 | # p3 = a0 + a1 + a2 12 | # p4 = a0 + a1 + a2 + a3 13 | # p5 = a0 + a1 + a2 + a3 + a4 14 | # p6 = a0 + a1 + a2 + a3 + a4 + a5 15 | # sum between index x and y 16 | # x = 3, y = 5 17 | # p6 - p3 18 | # p[y + 1] - p[x] 19 | 20 | # O(n * n) - # with prefix sums 21 | def slow_max_slice(a) 22 | max_slice = 0 23 | prefix = prefix_sum(a) 24 | a.size.times do |p| 25 | for q in p..(a.size - 1) 26 | sum = prefix[q + 1] - prefix[p] 27 | max_slice = [max_slice, sum].max 28 | end 29 | end 30 | max_slice 31 | end 32 | 33 | # O(n * n) --- w/o prefix sums 34 | def slow_maximal_slice(a) 35 | max_slice = 0 36 | a.size.times do |p| 37 | sum = 0 38 | for q in p..(a.size - 1) 39 | sum += a[q] 40 | max_slice = [max_slice, sum].max 41 | end 42 | end 43 | max_slice 44 | end 45 | 46 | # O(n) 47 | def fast_max_slice(a) 48 | max_slice = 0 # max sum of all slices so far 49 | 50 | # max ending compares max slice vs. max slice + current element 51 | # Do we let current element into max_slice sum or not? 52 | # We compare to 0 because if num makes max_ending < 0, 53 | # we just ignore all numbers in array up to num! Start fresh. 54 | max_ending = 0 55 | a.each do |num| 56 | max_ending = [0, max_ending + num].max 57 | max_slice = [max_slice, max_ending].max 58 | end 59 | max_slice 60 | end 61 | 62 | p fast_max_slice(a) -------------------------------------------------------------------------------- /merge_sort.rb: -------------------------------------------------------------------------------- 1 | a = [15, 8, 4, 12, 4, 3, 8, 15, 10, 6, 2, -1, 0] 2 | 3 | def merge_sort(a) 4 | return a if a.length <= 1 5 | middle = a.length / 2 6 | left = a[0..middle - 1] 7 | right = a[middle..-1] 8 | puts "left: #{left}, right: #{right}" 9 | left = merge_sort(left) 10 | right = merge_sort(right) 11 | merge(left, right) 12 | end 13 | 14 | def merge(left, right) 15 | result = [] 16 | while left.size > 0 && right.size > 0 17 | if left.first < right.first 18 | result << left.shift 19 | else 20 | result << right.shift 21 | end 22 | p result 23 | end 24 | 25 | if left.empty? 26 | result += right 27 | else 28 | result += left 29 | end 30 | result 31 | end 32 | 33 | p merge_sort(a) 34 | 35 | # 'left: [15, 8, 4, 12, 4, 3], right: [8, 15, 10, 6, 2, -1, 0] 36 | # left: [15, 8, 4], right: [12, 4, 3] 37 | # left: [15], right: [8, 4] 38 | # left: [8], right: [4] 39 | # [4] 40 | # [4] 41 | # [4, 8] 42 | # left: [12], right: [4, 3] 43 | # left: [4], right: [3] 44 | # [3] 45 | # [3] 46 | # [3, 4] 47 | # [3] 48 | # [3, 4] 49 | # [3, 4, 4] 50 | # [3, 4, 4, 8] 51 | # [3, 4, 4, 8, 12] 52 | # left: [8, 15, 10], right: [6, 2, -1, 0] 53 | # left: [8], right: [15, 10] 54 | # left: [15], right: [10] 55 | # [10] 56 | # [8] 57 | # left: [6, 2], right: [-1, 0] 58 | # left: [6], right: [2] 59 | # [2] 60 | # left: [-1], right: [0] 61 | # [-1] 62 | # [-1] 63 | # [-1, 0] 64 | # [-1] 65 | # [-1, 0] 66 | # [-1, 0, 2] 67 | # [-1, 0, 2, 6] 68 | # [-1] 69 | # [-1, 0] 70 | # [-1, 0, 2] 71 | # [-1, 0, 2, 3] 72 | # [-1, 0, 2, 3, 4] 73 | # [-1, 0, 2, 3, 4, 4] 74 | # [-1, 0, 2, 3, 4, 4, 6] 75 | # [-1, 0, 2, 3, 4, 4, 6, 8] 76 | # [-1, 0, 2, 3, 4, 4, 6, 8, 8] 77 | # [-1, 0, 2, 3, 4, 4, 6, 8, 8, 10] 78 | # [-1, 0, 2, 3, 4, 4, 6, 8, 8, 10, 12] 79 | # [-1, 0, 2, 3, 4, 4, 6, 8, 8, 10, 12, 15] 80 | # [-1, 0, 2, 3, 4, 4, 6, 8, 8, 10, 12, 15, 15]' -------------------------------------------------------------------------------- /permutation.rb: -------------------------------------------------------------------------------- 1 | # Permutation algorithms: https://www.cs.princeton.edu/~rs/talks/perms.pdf 2 | 3 | # recursive, no swaps 4 | # for array 5 | def permute(a) 6 | return [a] if a.size < 2 7 | perms = [] 8 | a.each do |elem| 9 | permute(a - [elem]).each do |p| 10 | perms << ([elem] + p) 11 | end 12 | end 13 | perms 14 | end 15 | 16 | # p permute([2, 5, 8, 9]) 17 | 18 | # [[2, 5, 8, 9], [2, 5, 9, 8], [2, 8, 5, 9], [2, 8, 9, 5], [2, 9, 5, 8], [2, 9, 8, 5], 19 | # [5, 2, 8, 9], [5, 2, 9, 8], [5, 8, 2, 9], [5, 8, 9, 2], [5, 9, 2, 8], [5, 9, 8, 2], 20 | # [8, 2, 5, 9], [8, 2, 9, 5], [8, 5, 2, 9], [8, 5, 9, 2], [8, 9, 2, 5], [8, 9, 5, 2], 21 | # [9, 2, 5, 8], [9, 2, 8, 5], [9, 5, 2, 8], [9, 5, 8, 2], [9, 8, 2, 5], [9, 8, 5, 2]] 22 | 23 | # for string 24 | # p permute('code'.split('')).map(&:join) 25 | 26 | # ["code", "coed", "cdoe", "cdeo", "ceod", "cedo", 27 | # "ocde", "oced", "odce", "odec", "oecd", "oedc", 28 | # "dcoe", "dceo", "doce", "doec", "deco", "deoc", 29 | # "ecod", "ecdo", "eocd", "eodc", "edco", "edoc"] 30 | 31 | # recursive, backtracking 32 | # for array. Has DUPLICATES. 33 | # 3 params: a = array, l = start index, r = end index 34 | # http://www.geeksforgeeks.org/write-a-c-program-to-print-all-permutations-of-a-given-string/ 35 | def permute(a, l, r) 36 | if l == r 37 | p a 38 | else 39 | a.size.times do |i| 40 | a[l], a[i] = a[i], a[l] 41 | permute(a, l + 1, r) 42 | a[l], a[i] = a[i], a[l] # backtracking 43 | end 44 | end 45 | end 46 | 47 | # Has duplicates: 48 | # [2, 1, 3] 49 | # [1, 2, 3] 50 | # [1, 3, 2] 51 | # [1, 2, 3] 52 | # [2, 1, 3] 53 | # [2, 3, 1] 54 | # [2, 3, 1] 55 | # [3, 2, 1] 56 | # [3, 1, 2] 57 | 58 | # recursive, backtracking 59 | # for string 60 | # store results in Set 61 | require 'set' 62 | def permute(a, l, r, ans = Set.new) 63 | puts "a: #{a}, l: #{l}, r: #{r}, ans: #{ans.to_a}" 64 | if l == r 65 | b = a.dup.join # without "dup," original array gets changed and ans has only 1 permutation 66 | ans.add(b) 67 | else 68 | a.size.times do |i| 69 | a[l], a[i] = a[i], a[l] 70 | permute(a, l + 1, r, ans) 71 | a[l], a[i] = a[i], a[l] # backtracking 72 | end 73 | end 74 | ans.to_a.sort 75 | end 76 | 77 | a = 'cod'.split('') 78 | # a = [1, 2, 3] 79 | l = 0 80 | r = a.size - 1 81 | p permute(a, l, r) 82 | 83 | # a: ["c", "o", "d"], l: 0, r: 2, ans: [] 84 | # a: ["c", "o", "d"], l: 1, r: 2, ans: [] 85 | # a: ["o", "c", "d"], l: 2, r: 2, ans: [] 86 | # a: ["c", "o", "d"], l: 2, r: 2, ans: ["ocd"] 87 | # a: ["c", "d", "o"], l: 2, r: 2, ans: ["ocd", "cod"] 88 | # a: ["o", "c", "d"], l: 1, r: 2, ans: ["ocd", "cod", "cdo"] 89 | # a: ["c", "o", "d"], l: 2, r: 2, ans: ["ocd", "cod", "cdo"] 90 | # a: ["o", "c", "d"], l: 2, r: 2, ans: ["ocd", "cod", "cdo"] 91 | # a: ["o", "d", "c"], l: 2, r: 2, ans: ["ocd", "cod", "cdo"] 92 | # a: ["d", "o", "c"], l: 1, r: 2, ans: ["ocd", "cod", "cdo", "odc"] 93 | # a: ["o", "d", "c"], l: 2, r: 2, ans: ["ocd", "cod", "cdo", "odc"] 94 | # a: ["d", "o", "c"], l: 2, r: 2, ans: ["ocd", "cod", "cdo", "odc"] 95 | # a: ["d", "c", "o"], l: 2, r: 2, ans: ["ocd", "cod", "cdo", "odc", "doc"] 96 | 97 | # ["cdo", "cod", "dco", "doc", "ocd", "odc"] 98 | 99 | # Best method: Heap's algorithm 100 | # https://en.wikipedia.org/wiki/Heap%27s_algorithm 101 | -------------------------------------------------------------------------------- /prefix_sums.rb: -------------------------------------------------------------------------------- 1 | # O(n) 2 | def prefix_sum(a) 3 | p = [0] 4 | (1..a.size).each do |k| 5 | p[k] = p[k - 1] + a[k - 1] 6 | end 7 | p 8 | end 9 | 10 | # p3 = a0 + a1 + a2 11 | # p4 = a0 + a1 + a2 + a3 12 | # p5 = a0 + a1 + a2 + a3 + a4 13 | # p6 = a0 + a1 + a2 + a3 + a4 + a5 14 | # sum between index x and y 15 | # x = 3, y = 5 16 | # p6 - p3 17 | # p[y + 1] - p[x] 18 | 19 | # total of 1 slice, O(1) 20 | # the totals of m slices [x..y] such that 0 <= from(x) <= to(y) < n, 21 | # total is ax + ax+1 + . . . + ay−1 + ay. 22 | def count_total(p, from, to) 23 | p[to + 1] - p[from] 24 | end -------------------------------------------------------------------------------- /prime_numbers.rb: -------------------------------------------------------------------------------- 1 | # find number of divisors of n 2 | 3 | # O(n) 4 | def slow_divisors(n) 5 | divisors = 0 6 | (1..n).each do |num| 7 | if n % num == 0 8 | divisors += 1 9 | end 10 | end 11 | divisors 12 | end 13 | 14 | # O(sqrt(n)) 15 | def divisors(n) 16 | divisors = 0 17 | i = 1 18 | while i * i < n 19 | divisors += 2 if n % i == 0 20 | i += 1 21 | end 22 | divisors +=1 if i * i == n 23 | divisors 24 | end 25 | 26 | # Is n prime? 27 | 28 | # O(n) 29 | def slow_prime?(n) 30 | (2..n - 1).each do |num| 31 | return false if n % num == 0 32 | end 33 | true 34 | end 35 | 36 | # O(sqrt(n)) 37 | def faster_prime?(n) 38 | i = 2 39 | while i * i < n 40 | return false if n % i == 0 41 | i += 1 42 | end 43 | true 44 | end 45 | 46 | # Each coin shows heads at first 47 | # Q: How many coins, after flipping, show tails? 48 | # After flipping 10 coins: [H, H, H,...H] 49 | # We get [T, H, H, T, H, H, H, H, T, H] 50 | # O(n * n) 51 | def slow_coins(a) 52 | n = a.size 53 | (1..n).each do |i| 54 | c = 1 55 | # person i flips all coins with multiples of i 56 | while c * i <= n 57 | a[c * i - 1] = a[c * i - 1] == 'H' ? 'T' : 'H' 58 | c += 1 59 | end 60 | end 61 | a.count('T') 62 | end 63 | 64 | a = ['H'] * 10 65 | puts "slow_coins. #{slow_coins(a)} tails" 66 | 67 | # O(n log n) 68 | def faster_coins(n) 69 | # n = num of coins 70 | # p = person # 71 | # coin = array of coins (0 = H, 1 = T) 72 | # k = array index of coin we're flipping 73 | # tails = # of tails after all coins flipped 74 | tails = 0 75 | coin = [0] * (n + 1) 76 | (1..n).each do |p| 77 | k = p 78 | while k <= n 79 | puts "p: #{p}, k: #{k}, coin: #{coin}" 80 | coin[k] = (coin[k] + 1) % 2 # flips 1 -> 0, 0 -> 1 81 | k += p 82 | end 83 | tails += coin[p] 84 | puts "tails: #{tails}" 85 | 86 | end 87 | puts "coin: #{coin}" 88 | tails 89 | end 90 | 91 | puts "faster_coins: #{faster_coins(10)} tails" 92 | 93 | # Each coin is flipped over exactly as many times as # of 94 | # its divisors. The coins flipped an odd number of times show tails, 95 | # so just find coins with ODD number of divisors. 96 | 97 | # Every # of form k*k has odd number of divisors. 98 | # There are exactly floor(sqrt(n)) numbers between 1 and n. 99 | # Answer: sqrt(n) rounded to nearest integer ("floor" function) = 100 | # number of PERFECT SQUARES <= n 101 | # See: math.stackexchange.com/questions/370828/number-of-perfect-squares-less-than-n 102 | 103 | # O(1) 104 | def fastest_coins(n) 105 | Math.sqrt(n).floor 106 | end 107 | 108 | puts "fastest_coins: #{fastest_coins(10)} tails" 109 | 110 | # slow_coins. 3 tails 111 | # p: 1, k: 1, coin: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 112 | # p: 1, k: 2, coin: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] 113 | # p: 1, k: 3, coin: [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0] 114 | # p: 1, k: 4, coin: [0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0] 115 | # p: 1, k: 5, coin: [0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0] 116 | # p: 1, k: 6, coin: [0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0] 117 | # p: 1, k: 7, coin: [0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0] 118 | # p: 1, k: 8, coin: [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0] 119 | # p: 1, k: 9, coin: [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0] 120 | # p: 1, k: 10, coin: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0] 121 | # tails: 1 122 | # p: 2, k: 2, coin: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 123 | # p: 2, k: 4, coin: [0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1] 124 | # p: 2, k: 6, coin: [0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1] 125 | # p: 2, k: 8, coin: [0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1] 126 | # p: 2, k: 10, coin: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1] 127 | # tails: 1 128 | # p: 3, k: 3, coin: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0] 129 | # p: 3, k: 6, coin: [0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0] 130 | # p: 3, k: 9, coin: [0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0] 131 | # tails: 1 132 | # p: 4, k: 4, coin: [0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0] 133 | # p: 4, k: 8, coin: [0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0] 134 | # tails: 2 135 | # p: 5, k: 5, coin: [0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0] 136 | # p: 5, k: 10, coin: [0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0] 137 | # tails: 2 138 | # p: 6, k: 6, coin: [0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1] 139 | # tails: 2 140 | # p: 7, k: 7, coin: [0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1] 141 | # tails: 2 142 | # p: 8, k: 8, coin: [0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1] 143 | # tails: 2 144 | # p: 9, k: 9, coin: [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1] 145 | # tails: 3 146 | # p: 10, k: 10, coin: [0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1] 147 | # tails: 3 148 | # coin: [0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0] 149 | # faster_coins: 3 tails 150 | # fastest_coins: 3 tails -------------------------------------------------------------------------------- /sieve_of_eratosthenes.rb: -------------------------------------------------------------------------------- 1 | # returns array of all primes up to n 2 | # nil means array index is prime (except 0, 1) 3 | def sieve(n) 4 | primes = [nil, nil] + (2..n).to_a 5 | i = 2 6 | while i * i <= n # i just must loop up to sqrt(n) 7 | if primes[i] 8 | k = i * i 9 | while k <= n 10 | primes[k] = nil 11 | k += i 12 | end 13 | end 14 | i += 1 15 | end 16 | primes.compact! 17 | end 18 | 19 | # start = Time.now 20 | # p sieve(19) 21 | # puts "#{Time.now - start} secs" # Took 0.3 secs 22 | 23 | # prepare array f for prime factorization 24 | # For every crossed number we remember the smallest prime that divides this number. 25 | def array_f(n) 26 | # 0 means array index is prime (except 0, 1) 27 | f = [0] * (n + 1) 28 | i = 2 29 | while i * i <= n 30 | if f[i] == 0 31 | k = i * i 32 | while k <= n 33 | f[k] = i if f[k] == 0 34 | k += i 35 | end 36 | end 37 | i += 1 38 | end 39 | f 40 | end 41 | 42 | # p array_f(20) 43 | # [0, 0, 0, 0, 2, 0, 2, 0, 2, 3, 2, 0, 2, 0, 2, 3, 2, 0, 2, 0, 2] 44 | 45 | # If p = a prime factor of n, then all the prime factors of n = 46 | # p plus prime factors of n/p 47 | # O(log(n)) 48 | def prime_factors_bad(n) # <--BAD! Uses up TON of memory! 49 | f = array_f(n) 50 | pfactors = [] 51 | while f[n] > 0 52 | pfactors << f[n] 53 | n /= f[n] 54 | end 55 | pfactors << n 56 | pfactors 57 | end 58 | 59 | # From Project Euler #3 60 | def prime_factors(n) 61 | factors = [] 62 | f = 2 63 | while f * f <= n 64 | if n % f == 0 65 | factors << f 66 | n /= f 67 | else 68 | f += 1 69 | end 70 | end 71 | factors << n 72 | factors 73 | end 74 | 75 | # can't run! Used over 20 GB RAM! 76 | #p prime_factors_bad(600851475143) 77 | p prime_factors(600851475143) # <- num from Project Euler 78 | # [71, 839, 1471, 6857] 79 | -------------------------------------------------------------------------------- /solution-flags.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayning0/codility/afdc53897602fb65d232b4047cdbdf405fa105a1/solution-flags.pdf -------------------------------------------------------------------------------- /subset_sum.rb: -------------------------------------------------------------------------------- 1 | # Take array and return result if any combo of numbers in array add up to a certain sum, else return false. Example: if arr contains [4, 6, 10, 1, 3] and sum = 23, output = [4, 6, 10, 3] since 4 + 6 + 10 + 3 = 23. The array will not be empty, will not contain all the same elements, and may contain negative numbers. 2 | 3 | # Subset Sum: special case of knapsack problem 4 | 5 | # https://livinglifetechway.com/solving-subset-sum-problem/ 6 | # http://www.skorks.com/2011/02/algorithms-a-dropbox-challenge-and-dynamic-programming/ 7 | # https://www.youtube.com/watch?v=zKwwjAkaXLI 8 | 9 | a = [4, 6, 10, 1, 3] 10 | require 'rspec/autorun' 11 | 12 | # Recursive: Works for ALL POSITIVE numbers: 13 | def subset_sum1(a, sum, result = []) 14 | #puts "a: #{a}, sum: #{sum}, result: #{result}" 15 | return result if sum == 0 # found answer 16 | if sum < 0 || a.size == 0 17 | result.pop 18 | return false 19 | end 20 | subset_sum1(a[1..-1], sum - a[0], result << a[0]) || # try next element 21 | subset_sum1(a[1..-1], sum, result) # reject next element 22 | end 23 | 24 | # p subset_sum1(a, 23) 25 | 26 | # a: [4, 6, 10, 1, 3], sum: 23, result: [] 27 | # a: [6, 10, 1, 3], sum: 19, result: [4] 28 | # a: [10, 1, 3], sum: 13, result: [4, 6] 29 | # a: [1, 3], sum: 3, result: [4, 6, 10] 30 | # a: [3], sum: 2, result: [4, 6, 10, 1] 31 | # a: [], sum: -1, result: [4, 6, 10, 1, 3] 32 | # a: [], sum: 2, result: [4, 6, 10, 1] 33 | # a: [3], sum: 3, result: [4, 6, 10] 34 | # a: [], sum: 0, result: [4, 6, 10, 3] 35 | # [4, 6, 10, 3] 36 | 37 | # p subset_sum1(a, 22) 38 | 39 | # a: [4, 6, 10, 1, 3], sum: 22, result: [] 40 | # a: [6, 10, 1, 3], sum: 18, result: [4] 41 | # a: [10, 1, 3], sum: 12, result: [4, 6] 42 | # a: [1, 3], sum: 2, result: [4, 6, 10] 43 | # a: [3], sum: 1, result: [4, 6, 10, 1] 44 | # a: [], sum: -2, result: [4, 6, 10, 1, 3] 45 | # a: [], sum: 1, result: [4, 6, 10, 1] 46 | # a: [3], sum: 2, result: [4, 6, 10] 47 | # a: [], sum: -1, result: [4, 6, 10, 3] 48 | # a: [], sum: 2, result: [4, 6, 10] 49 | # a: [1, 3], sum: 12, result: [4, 6] 50 | # a: [3], sum: 11, result: [4, 6, 1] 51 | # a: [], sum: 8, result: [4, 6, 1, 3] 52 | # a: [], sum: 11, result: [4, 6, 1] 53 | # a: [3], sum: 12, result: [4, 6] 54 | # a: [], sum: 9, result: [4, 6, 3] 55 | # a: [], sum: 12, result: [4, 6] 56 | # a: [10, 1, 3], sum: 18, result: [4] 57 | # a: [1, 3], sum: 8, result: [4, 10] 58 | # a: [3], sum: 7, result: [4, 10, 1] 59 | # a: [], sum: 4, result: [4, 10, 1, 3] 60 | # a: [], sum: 7, result: [4, 10, 1] 61 | # a: [3], sum: 8, result: [4, 10] 62 | # a: [], sum: 5, result: [4, 10, 3] 63 | # a: [], sum: 8, result: [4, 10] 64 | # a: [1, 3], sum: 18, result: [4] 65 | # a: [3], sum: 17, result: [4, 1] 66 | # a: [], sum: 14, result: [4, 1, 3] 67 | # a: [], sum: 17, result: [4, 1] 68 | # a: [3], sum: 18, result: [4] 69 | # a: [], sum: 15, result: [4, 3] 70 | # a: [], sum: 18, result: [4] 71 | # a: [6, 10, 1, 3], sum: 22, result: [] 72 | # a: [10, 1, 3], sum: 16, result: [6] 73 | # a: [1, 3], sum: 6, result: [6, 10] 74 | # a: [3], sum: 5, result: [6, 10, 1] 75 | # a: [], sum: 2, result: [6, 10, 1, 3] 76 | # a: [], sum: 5, result: [6, 10, 1] 77 | # a: [3], sum: 6, result: [6, 10] 78 | # a: [], sum: 3, result: [6, 10, 3] 79 | # a: [], sum: 6, result: [6, 10] 80 | # a: [1, 3], sum: 16, result: [6] 81 | # a: [3], sum: 15, result: [6, 1] 82 | # a: [], sum: 12, result: [6, 1, 3] 83 | # a: [], sum: 15, result: [6, 1] 84 | # a: [3], sum: 16, result: [6] 85 | # a: [], sum: 13, result: [6, 3] 86 | # a: [], sum: 16, result: [6] 87 | # a: [10, 1, 3], sum: 22, result: [] 88 | # a: [1, 3], sum: 12, result: [10] 89 | # a: [3], sum: 11, result: [10, 1] 90 | # a: [], sum: 8, result: [10, 1, 3] 91 | # a: [], sum: 11, result: [10, 1] 92 | # a: [3], sum: 12, result: [10] 93 | # a: [], sum: 9, result: [10, 3] 94 | # a: [], sum: 12, result: [10] 95 | # a: [1, 3], sum: 22, result: [] 96 | # a: [3], sum: 21, result: [1] 97 | # a: [], sum: 18, result: [1, 3] 98 | # a: [], sum: 21, result: [1] 99 | # a: [3], sum: 22, result: [] 100 | # a: [], sum: 19, result: [3] 101 | # a: [], sum: 22, result: [] 102 | # false 103 | 104 | # Recursive: Works for positive AND negative numbers 105 | def subset_sum2(a, sum, result = []) 106 | # puts "a: #{a}, sum: #{sum}, result: #{result}" 107 | return result if sum == 0 # got answer 108 | if a.size == 0 109 | result.pop 110 | return false 111 | end 112 | subset_sum2(a[1..-1], sum - a[0], result << a[0]) || # try next element 113 | subset_sum2(a[1..-1], sum, result) # reject next element 114 | end 115 | 116 | #p subset_sum2(a, -11) 117 | 118 | # a: [4, 6, -9, 1, -3], sum: -11, result: [] 119 | # a: [6, -9, 1, -3], sum: -15, result: [4] 120 | # a: [-9, 1, -3], sum: -21, result: [4, 6] 121 | # a: [1, -3], sum: -12, result: [4, 6, -9] 122 | # a: [-3], sum: -13, result: [4, 6, -9, 1] 123 | # a: [], sum: -10, result: [4, 6, -9, 1, -3] 124 | # a: [], sum: -13, result: [4, 6, -9, 1] 125 | # a: [-3], sum: -12, result: [4, 6, -9] 126 | # a: [], sum: -9, result: [4, 6, -9, -3] 127 | # a: [], sum: -12, result: [4, 6, -9] 128 | # a: [1, -3], sum: -21, result: [4, 6] 129 | # a: [-3], sum: -22, result: [4, 6, 1] 130 | # a: [], sum: -19, result: [4, 6, 1, -3] 131 | # a: [], sum: -22, result: [4, 6, 1] 132 | # a: [-3], sum: -21, result: [4, 6] 133 | # a: [], sum: -18, result: [4, 6, -3] 134 | # a: [], sum: -21, result: [4, 6] 135 | # a: [-9, 1, -3], sum: -15, result: [4] 136 | # a: [1, -3], sum: -6, result: [4, -9] 137 | # a: [-3], sum: -7, result: [4, -9, 1] 138 | # a: [], sum: -4, result: [4, -9, 1, -3] 139 | # a: [], sum: -7, result: [4, -9, 1] 140 | # a: [-3], sum: -6, result: [4, -9] 141 | # a: [], sum: -3, result: [4, -9, -3] 142 | # a: [], sum: -6, result: [4, -9] 143 | # a: [1, -3], sum: -15, result: [4] 144 | # a: [-3], sum: -16, result: [4, 1] 145 | # a: [], sum: -13, result: [4, 1, -3] 146 | # a: [], sum: -16, result: [4, 1] 147 | # a: [-3], sum: -15, result: [4] 148 | # a: [], sum: -12, result: [4, -3] 149 | # a: [], sum: -15, result: [4] 150 | # a: [6, -9, 1, -3], sum: -11, result: [] 151 | # a: [-9, 1, -3], sum: -17, result: [6] 152 | # a: [1, -3], sum: -8, result: [6, -9] 153 | # a: [-3], sum: -9, result: [6, -9, 1] 154 | # a: [], sum: -6, result: [6, -9, 1, -3] 155 | # a: [], sum: -9, result: [6, -9, 1] 156 | # a: [-3], sum: -8, result: [6, -9] 157 | # a: [], sum: -5, result: [6, -9, -3] 158 | # a: [], sum: -8, result: [6, -9] 159 | # a: [1, -3], sum: -17, result: [6] 160 | # a: [-3], sum: -18, result: [6, 1] 161 | # a: [], sum: -15, result: [6, 1, -3] 162 | # a: [], sum: -18, result: [6, 1] 163 | # a: [-3], sum: -17, result: [6] 164 | # a: [], sum: -14, result: [6, -3] 165 | # a: [], sum: -17, result: [6] 166 | # a: [-9, 1, -3], sum: -11, result: [] 167 | # a: [1, -3], sum: -2, result: [-9] 168 | # a: [-3], sum: -3, result: [-9, 1] 169 | # a: [], sum: 0, result: [-9, 1, -3] 170 | # [-9, 1, -3] 171 | 172 | #p subset_sum2(a, 12) 173 | 174 | # a: [4, 6, -9, 1, -3], sum: 12, result: [] 175 | # a: [6, -9, 1, -3], sum: 8, result: [4] 176 | # a: [-9, 1, -3], sum: 2, result: [4, 6] 177 | # a: [1, -3], sum: 11, result: [4, 6, -9] 178 | # a: [-3], sum: 10, result: [4, 6, -9, 1] 179 | # a: [], sum: 13, result: [4, 6, -9, 1, -3] 180 | # a: [], sum: 10, result: [4, 6, -9, 1] 181 | # a: [-3], sum: 11, result: [4, 6, -9] 182 | # a: [], sum: 14, result: [4, 6, -9, -3] 183 | # a: [], sum: 11, result: [4, 6, -9] 184 | # a: [1, -3], sum: 2, result: [4, 6] 185 | # a: [-3], sum: 1, result: [4, 6, 1] 186 | # a: [], sum: 4, result: [4, 6, 1, -3] 187 | # a: [], sum: 1, result: [4, 6, 1] 188 | # a: [-3], sum: 2, result: [4, 6] 189 | # a: [], sum: 5, result: [4, 6, -3] 190 | # a: [], sum: 2, result: [4, 6] 191 | # a: [-9, 1, -3], sum: 8, result: [4] 192 | # a: [1, -3], sum: 17, result: [4, -9] 193 | # a: [-3], sum: 16, result: [4, -9, 1] 194 | # a: [], sum: 19, result: [4, -9, 1, -3] 195 | # a: [], sum: 16, result: [4, -9, 1] 196 | # a: [-3], sum: 17, result: [4, -9] 197 | # a: [], sum: 20, result: [4, -9, -3] 198 | # a: [], sum: 17, result: [4, -9] 199 | # a: [1, -3], sum: 8, result: [4] 200 | # a: [-3], sum: 7, result: [4, 1] 201 | # a: [], sum: 10, result: [4, 1, -3] 202 | # a: [], sum: 7, result: [4, 1] 203 | # a: [-3], sum: 8, result: [4] 204 | # a: [], sum: 11, result: [4, -3] 205 | # a: [], sum: 8, result: [4] 206 | # a: [6, -9, 1, -3], sum: 12, result: [] 207 | # a: [-9, 1, -3], sum: 6, result: [6] 208 | # a: [1, -3], sum: 15, result: [6, -9] 209 | # a: [-3], sum: 14, result: [6, -9, 1] 210 | # a: [], sum: 17, result: [6, -9, 1, -3] 211 | # a: [], sum: 14, result: [6, -9, 1] 212 | # a: [-3], sum: 15, result: [6, -9] 213 | # a: [], sum: 18, result: [6, -9, -3] 214 | # a: [], sum: 15, result: [6, -9] 215 | # a: [1, -3], sum: 6, result: [6] 216 | # a: [-3], sum: 5, result: [6, 1] 217 | # a: [], sum: 8, result: [6, 1, -3] 218 | # a: [], sum: 5, result: [6, 1] 219 | # a: [-3], sum: 6, result: [6] 220 | # a: [], sum: 9, result: [6, -3] 221 | # a: [], sum: 6, result: [6] 222 | # a: [-9, 1, -3], sum: 12, result: [] 223 | # a: [1, -3], sum: 21, result: [-9] 224 | # a: [-3], sum: 20, result: [-9, 1] 225 | # a: [], sum: 23, result: [-9, 1, -3] 226 | # a: [], sum: 20, result: [-9, 1] 227 | # a: [-3], sum: 21, result: [-9] 228 | # a: [], sum: 24, result: [-9, -3] 229 | # a: [], sum: 21, result: [-9] 230 | # a: [1, -3], sum: 12, result: [] 231 | # a: [-3], sum: 11, result: [1] 232 | # a: [], sum: 14, result: [1, -3] 233 | # a: [], sum: 11, result: [1] 234 | # a: [-3], sum: 12, result: [] 235 | # a: [], sum: 15, result: [-3] 236 | # a: [], sum: 12, result: [] 237 | # false 238 | 239 | # Dynamic Programming, assuming all POSITIVE numbers 240 | def subset_sum_dp1(a, sum) 241 | result = [] 242 | max_sum = a.sum 243 | return [] if a.size == 0 || !sum.between?(0, max_sum) || sum == 0 244 | width = [a.max, sum].max + 1 245 | 246 | m = Array.new(a.size) { Array.new(width, 0) } 247 | 248 | a.size.times do |row| 249 | # Fill col 1, since we get 0 sum for all numbers by excluding them all 250 | m[row][0] = 1 251 | end 252 | 253 | m[0][a[0]] = 1 # Fill row 1. Put 1 (True) at index of first element. 254 | 255 | (1..a.size - 1).each do |row| 256 | (1..sum).each do |col| 257 | 258 | # use | (bit operator OR) to compare 0 and 1 259 | m_up_left = if col - a[row] >= 0 260 | m[row - 1][col - a[row]] 261 | else 262 | 0 263 | end 264 | m[row][col] = m[row - 1][col] | m_up_left 265 | end 266 | end 267 | puts "a: #{a}. max_sum: #{max_sum}. Can we sum to #{sum}?" 268 | p (0..width - 1).to_a # print col numbers 269 | pp m # print final matrix 270 | 271 | row = a.size - 1 272 | col = sum 273 | 274 | if m[row][col] == 0 # did not find subset sum 275 | puts 'No' 276 | return [] 277 | end 278 | 279 | # Build sum result array from final matrix 280 | while row >= 0 && col >= 0 281 | # puts "row:#{row}, col:#{col}" 282 | if m[row][col] == 1 283 | if row == 0 && col > 0 284 | result << a[0] 285 | break 286 | end 287 | 288 | (row - 1).downto(0).each do |r| 289 | # puts "r: #{r}, m[#{r}][#{col}]: #{m[r][col]}" 290 | if m[r][col] == 0 291 | result << a[r + 1] 292 | row = r 293 | break 294 | end 295 | 296 | if r == 0 && col > 0 && m[r][col] == 1 297 | result << a[0] 298 | row = r 299 | end 300 | end 301 | 302 | col -= a[row + 1] 303 | else 304 | break 305 | end 306 | end 307 | p result 308 | result 309 | end 310 | 311 | # a: [4, 6, 10, 1, 3]. max_sum: 24. Can we sum to 22? 312 | # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22] 313 | # [[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 314 | # [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 315 | # [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0], 316 | # [1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0], 317 | # [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]] 318 | # No 319 | 320 | # a: [4, 6, 10, 1, 3]. max_sum: 24. Can we sum to 23? 321 | # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] 322 | # [[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 323 | # [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 324 | # [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0], 325 | # [1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0], 326 | # [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1]] 327 | # [3, 10, 6, 4] 328 | 329 | # a: [4, 6, 10, 1, 3]. max_sum: 24. Can we sum to 24? 330 | # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24] 331 | # [[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 332 | # [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 333 | # [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0], 334 | # [1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0], 335 | # [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1]] 336 | # [3, 1, 10, 6, 4] 337 | 338 | # Dynamic Programming, for positive AND negative numbers 339 | def subset_sum_dp2(a, sum) 340 | if a.size == 1 341 | return sum == a.first ? a : [] 342 | end 343 | 344 | min_sum, max_sum = 0, 0 345 | a.each do |num| 346 | if num > 0 347 | max_sum += num 348 | else 349 | min_sum += num 350 | end 351 | end 352 | 353 | return [] if a.size == 0 || !sum.between?(min_sum, max_sum) || sum == 0 354 | 355 | # map min_sum => 0, max_sum => max_sum - min_sum 356 | # sum => sum - min_sum 357 | 358 | result = [] 359 | 360 | width = max_sum - min_sum + 1 361 | min_num = a[1..-1].min 362 | if a.size > 1 && min_num < 0 363 | width -= min_num # expands width of 2D matrix for negative numbers 364 | end 365 | 366 | m = Array.new(a.size) { Array.new(width, 0) } 367 | 368 | if 0.between?(min_sum, max_sum) 369 | a.size.times do |row| 370 | # Fill 0 col, since we get 0 sum for all numbers by excluding them all 371 | m[row][-min_sum] = 1 372 | end 373 | end 374 | 375 | m[0][a[0] - min_sum] = 1 # Fill row 1. Put 1 (True) at index of first element. 376 | 377 | (1..a.size - 1).each do |row| 378 | (0..max_sum - min_sum).each do |col| 379 | 380 | # use | (bit operator OR) to compare 0 and 1 381 | m_up = if col - a[row] >= 0 382 | m[row - 1][col - a[row]] 383 | else 384 | 0 385 | end 386 | m[row][col] = m[row - 1][col] | m_up 387 | end 388 | end 389 | puts "a: #{a}. max_sum: #{max_sum}. Can we sum to #{sum}?" 390 | if sum == -12 391 | p (0..width - 1).to_a # print col numbers 392 | p (min_sum..max_sum).to_a # actual col numbers 393 | #pp m # print final matrix 394 | end 395 | 396 | row = a.size - 1 397 | col = sum - min_sum 398 | 399 | if m[row][col] == 0 # did not find subset sum 400 | puts 'No' 401 | return [] 402 | end 403 | 404 | # Build sum result array from final matrix 405 | while row >= 0 && col >= 0 406 | if m[row][col] == 1 407 | if row == 0 && col != -min_sum 408 | result << a[0] 409 | break 410 | end 411 | 412 | (row - 1).downto(0).each do |r| 413 | if m[r][col] == 0 414 | result << a[r + 1] 415 | row = r 416 | break 417 | end 418 | 419 | if r == 0 && col != -min_sum && m[r][col] == 1 420 | result << a[0] 421 | row = r 422 | end 423 | end 424 | 425 | col -= a[row + 1] 426 | else 427 | break 428 | end 429 | end 430 | p result 431 | result 432 | end 433 | 434 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to -12? 435 | # [-3, -9] 436 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to -11? 437 | # [-3, 1, -9] 438 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to -9? 439 | # [-9] 440 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to -8? 441 | # [1, -9] 442 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to -7? 443 | # [-3, 1, -9, 4] 444 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to -6? 445 | # [-3, -9, 6] 446 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to -5? 447 | # [-9, 4] 448 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to -4? 449 | # [1, -9, 4] 450 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to -3? 451 | # [-9, 6] 452 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to -2? 453 | # [1, -9, 6] 454 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to -1? 455 | # [-3, 1, -9, 6, 4] 456 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to 1? 457 | # [-9, 6, 4] 458 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to 2? 459 | # [1, -9, 6, 4] 460 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to 3? 461 | # [-3, 6] 462 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to 4? 463 | # [4] 464 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to 5? 465 | # [1, 4] 466 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to 6? 467 | # [6] 468 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to 7? 469 | # [1, 6] 470 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to 8? 471 | # [-3, 1, 6, 4] 472 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to 10? 473 | # [6, 4] 474 | # a: [4, 6, -9, 1, -3]. max_sum: 11. Can we sum to 11? 475 | # [1, 6, 4] 476 | 477 | # ALL TESTS PASS! 478 | describe 'Subset Sum' do 479 | let(:a) { [4, 6, 10, 1, 3] } 480 | let(:b) { [4, 6, -9, 1, -3] } 481 | let(:no_subset_a) { [0, 2, 12, 22] } 482 | let(:no_subset_b) { [-10, 9] } 483 | 484 | describe '#subset_sum_dp' do 485 | describe 'finds no subset sum answers' do 486 | it 'if array all positive' do 487 | no_subset_a.each do |sum| 488 | expect(subset_sum_dp1(a, sum)).to eq [] 489 | expect(subset_sum1(a, sum)).to eq([]).or eq false 490 | expect(subset_sum2(a, sum)).to eq([]).or eq false 491 | end 492 | end 493 | 494 | it 'if array includes negatives' do 495 | no_subset_b.each do |sum| 496 | expect(subset_sum2(b, sum)).to eq([]).or eq false 497 | expect(subset_sum_dp2(b, sum)).to eq([]).or eq false 498 | end 499 | end 500 | end 501 | 502 | describe 'finds subset sum answers' do 503 | it 'if array all positive' do 504 | (a.sum + 1).times do |s| 505 | unless no_subset_a.include?(s) 506 | expect(subset_sum_dp1(a, s).sum).to eq s 507 | expect(subset_sum1(a, s).sum).to eq s 508 | expect(subset_sum2(a, s).sum).to eq s 509 | end 510 | end 511 | end 512 | 513 | it 'if array includes negatives' do 514 | (-12..11).each do |s| 515 | unless no_subset_b.include?(s) 516 | expect(subset_sum2(b, s).sum).to eq s 517 | expect(subset_sum_dp2(b, s).sum).to eq s 518 | end 519 | end 520 | end 521 | end 522 | end 523 | end 524 | --------------------------------------------------------------------------------