Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Section 1 Improvements #6

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
9 changes: 5 additions & 4 deletions src/chapter1/ch1-q1.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
* @return {boolean} True if unique characters, otherwise false
*/
export function hasUniqueCharactersSet(str) {
let chars = new Set();
const chars = new Set();

for (let i = 0; i < str.length; ++i) {
if (chars.has(str[i])) {
for (const ch of str) {
if (chars.has(ch)) {
return false;
}
chars.add(str[i]);
chars.add(ch);
}

return true;
}

Expand Down
32 changes: 17 additions & 15 deletions src/chapter1/ch1-q2.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,17 @@ export function isPermutationMap(str1, str2) {

let chars = new Map();

for (let i = 0; i < str1.length; ++i) {
chars.set(str1[i], chars.get(str1[i]) + 1 || 1); // increment or set to 1
for (const ch of str1) {
chars.set(ch, chars.get(ch) + 1 || 1); // increment or set to 1
}

for (let i = 0; i < str2.length; ++i) {
let count = chars.get(str2[i]);
if (!count) {
return false;
}
if (count === 1) {
chars.delete(str2[i]);
}
else {
chars.set(str2[i], count - 1);
for (const ch of str2) {
if (!chars.has(ch)) return false; // shortcircuit if a char doesn't exist in both strings
const nextCount = chars.get(ch) - 1;
if (nextCount === 0) {
chars.delete(ch);
} else {
chars.set(ch, nextCount);
}
}

Expand All @@ -57,8 +54,13 @@ export function isPermutationSorted(str1, str2) {
return false;
}
// sort string using quicksort
str1.sort();
str2.sort();
const sorted1 = sortString(str1);
const sorted2 = sortString(str2);

return sorted1.every((v, i) => v === sorted2[i]);
}

return str1.every((v, i) => v === str2[i]);
// for the purposes of the question, ignore array creation and assume O(1) space sort as with arrays
function sortString(str) {
return Array.from(str).sort();
}
13 changes: 7 additions & 6 deletions src/chapter1/ch1-q2.spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { expect } from 'chai';
import * as funcs from './ch1-q2';

import { expect } from 'chai';

for (let key in funcs) {
let func = funcs[key];

describe('ch1-q2: ' + key, function() {
describe('ch1-q2: ' + key, function () {

[
['abcdefghi', 'ihgfedcba'],
Expand All @@ -13,8 +14,8 @@ for (let key in funcs) {
['icarraci', 'carcarii']
].forEach(args => {

it(`returns true for strings that are permutations: '${args[0]}' & '${args[1]}'`, function() {
expect(func(args[0].split(''), args[1].split(''))).to.be.true;
it(`returns true for strings that are permutations: '${args[0]}' & '${args[1]}'`, function () {
expect(func(args[0], args[1])).to.be.true;
});

});
Expand All @@ -26,8 +27,8 @@ for (let key in funcs) {
['45678', '1239']
].forEach(args => {

it(`returns false for strings that are not permutations: '${args[0]}' & '${args[1]}'`, function() {
expect(func(args[0].split(''), args[1].split(''))).to.be.false;
it(`returns false for strings that are not permutations: '${args[0]}' & '${args[1]}'`, function () {
expect(func(args[0], args[1])).to.be.false;
});

});
Expand Down
4 changes: 2 additions & 2 deletions src/chapter1/ch1-q3.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export function encodeSpaces(url) {
}

let spaceCount = 0;
for (let i = 0; i < url.length; ++i) {
if (url[i] === ' ') {
for (let ch of url) {
if (ch === ' ') {
++spaceCount;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/chapter1/ch1-q4.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export function isPalindromePermutationsSet(str) {
return false;
}

let chars = new Set();
for (let char of str) {
const chars = new Set();
for (const char of str) {
if (char !== ' ') { // ignore spaces
if (chars.has(char)) {
chars.delete(char);
Expand Down
69 changes: 63 additions & 6 deletions src/chapter1/ch1-q6.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,73 @@
* Time: O(N)
* Additional space: O(N)
*
* @param {string} str [description]
* @return {[type]} [description]
* @param {string} str The given string
* @return {string} The compressed string or the original string
*/
export function compressString(str) {
if (!str) {
return str;
}

let cStr = '';
let compressed = '';
let countConsecutive = 0;
for (let i = 0; i < str.length; ++i) {
++countConsecutive;

if (i + 1 >= str.length || str[i] !== str[i + 1]) {
// JS does not have a StringBuilder/StringBuffer style class for creating strings
// string concatenation has been heavily optimised in JS implementations and
// is faster than creating a string via an array then using a .join('') at the end
compressed += str[i] + countConsecutive;
countConsecutive = 0;
}
}

return compressed.length < str.length ? compressed : str;
}

/**
* Takes an input string and counts contiguous sequences of the same character
* and replaces them with XC (X = count, C = character).
*
* It runs a first pass through the string to determine the compression length.
* If the compression length is not smaller than current length, we can just
* return the given string without ever creating the compressed version.
*
* N = |str|
* Time: O(N)
* Additional space: O(N)
*
* @param {string} str The given string
* @return {string} The compressed string or the original string
*/
export function compressStringOnlyIfNecessary(str) {
if (!str) {
return str;
}

const compressedLength = getCompressionLength(str);
return compressedLength < str.length ?
compressStringOnlyIfNecessaryHelper(str) :
str;
}

function getCompressionLength(str) {
let compressedLength = 0,
countConsecutive = 0;
for (let i = 0; i < str.length; ++i) {
++countConsecutive;

if (i + 1 >= str.length || str[i] !== str[i + 1]) {
compressedLength += 1 + String(countConsecutive).length;
countConsecutive = 0;
}
}
return compressedLength;
}

function compressStringOnlyIfNecessaryHelper(str) {
let compressed = '';
for (let i = 0; i < str.length; ++i) {
let char = str[i],
start = i;
Expand All @@ -26,8 +84,7 @@ export function compressString(str) {
// JS does not have a StringBuilder/StringBuffer style class for creating strings
// string concatenation has been heavily optimised in JS implementations and
// is faster than creating a string via an array then using a .join('') at the end
cStr += (i - start + 1) + char;
compressed += char + (i - start + 1);
}

return cStr.length < str.length ? cStr : str;
return compressed;
}
26 changes: 14 additions & 12 deletions src/chapter1/ch1-q6.spec.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { expect } from 'chai';
import * as funcs from './ch1-q6';

import { expect } from 'chai';

for (let key in funcs) {
let func = funcs[key];

describe('ch1-q6: ' + key, function() {
describe('ch1-q6: ' + key, function () {

it('returns input where null/undefined', function() {
it('returns input where null/undefined', function () {
expect(func(null)).to.be.null;
expect(func(undefined)).to.be.undefined;
});

it('returns input where empty string', function() {
it('returns input where empty string', function () {
expect(func('')).to.equal('');
});

Expand All @@ -23,22 +24,23 @@ for (let key in funcs) {
'ababababccab'
].forEach(arg => {

it(`returns input string where compression doesn't use less space: '${arg}'`, function() {
it(`returns input string where compression doesn't use less space: '${arg}'`, function () {
expect(func(arg)).to.eql(arg);
});

});

[
{ arg: 'aaa', out: '3a' },
{ arg: 'bbbbbb', out: '6b' },
{ arg: 'abbbbbbc', out: '1a6b1c' },
{ arg: 'aaabccc', out: '3a1b3c' },
{ arg: 'hhellllllllooooo!', out: '2h1e8l5o1!' },
{ arg: 'woorrrllllddddd', out: '1w2o3r4l5d' }
{ arg: 'aaa', out: 'a3' },
{ arg: 'bbbbbb', out: 'b6' },
{ arg: 'abbbbbbc', out: 'a1b6c1' },
{ arg: 'aaabccc', out: 'a3b1c3' },
{ arg: 'hhellllllllooooo!', out: 'h2e1l8o5!1' },
{ arg: 'woorrrllllddddd', out: 'w1o2r3l4d5' },
{ arg: 'aaaaaaaaaaabbbbbbbbbbbb', out: 'a11b12' }
].forEach(context => {

it(`returns ${context.out} with string ${context.arg}`, function() {
it(`returns ${context.out} with string ${context.arg}`, function () {
expect(func(context.arg)).to.eql(context.out);
});

Expand Down
90 changes: 89 additions & 1 deletion src/chapter1/ch1-q8.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* @return {array} Matrix that has been zeroed, same object as input
*/
export function zeroMatrix(matrix) {
if (!matrix) {
if (!Array.isArray(matrix) || !Array.isArray(matrix[0])) {
throw new Error('invalid matrix');
}
if (matrix.length === 0) {
Expand Down Expand Up @@ -47,3 +47,91 @@ export function zeroMatrix(matrix) {

return matrix;
}

/**
* Instead of using two extra arrays as storage for rows and columns that will be
* nullified, use the first row and first column.
*
* N = matrix Y dimension
* M = matrix X dimension
* Time: O(N * M)
* Additional space: O(1)
*
* @param {array} matrix Matrix to be zeroed in-place
* @return {array} Matrix that has been zeroed, same object as input
*/
export function zeroMatrixConstantSpace(matrix) {
if (!Array.isArray(matrix) || !Array.isArray(matrix[0])) {
throw new Error('invalid matrix');
}
if (matrix.length === 0) {
return matrix;
}

// check if first row has a zero
let firstRowHasZero = false;
for (let col = 0; col < matrix[0].length; col++) {
if (matrix[0][col] === 0) {
firstRowHasZero = true;
break;
}
}

// check if first column has a zero
let firstColHasZero;
for (let row = 0; row < matrix.length; row++) {
if (matrix[row][0] === 0) {
firstColHasZero = true;
break;
}
}

// check for zeros in the rest of the rows/cols and update
// first row/column storage accordingly
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[0].length; col++) {
if (matrix[row][col] === 0) {
matrix[row][0] = 0;
matrix[0][col] = 0;
}
}
}

// nullify rows based on the values in the first column
for (let row = 1; row < matrix.length; row++) {
if (matrix[row][0] === 0) {
nullifyRow(matrix, row);
}
}

// nullify columns based on the values in the first row
for (let col = 1; col < matrix[0].length; col++) {
if (matrix[0][col] === 0) {
nullifyCol(matrix, col);
}
}

// nullify first row if necessary
if (firstRowHasZero) {
nullifyRow(matrix, 0);
}

// nullify first col if necessary
if (firstColHasZero) {
nullifyCol(matrix, 0);
}

return matrix;
}

function nullifyRow(matrix, row) {
for (let col = 0; col < matrix[row].length; col++) {
matrix[row][col] = 0;
}
}

function nullifyCol(matrix, col) {
for (let row = 0; row < matrix.length; row++) {
matrix[row][col] = 0;
}
}