LeetCode 189 - Rotate Array
$begingroup$
I have a working solution for this problem that was accepted in LeetCode:
Given an array, rotate the array to the right by k steps, where k is non-negative."
function rotate(nums, k) {
for (let i = 1; i <= k; i += 1) {
const poppedNum = nums.pop();
nums.unshift(poppedNum);
}
return nums;
}
// rotate([1, 2, 3, 4, 5, 6, 7], 3) -> [5, 6, 7, 1, 2, 3, 4]
Questions:
Would the time complexity be $O(k)$ and space complexity be $O(1)$?
Is there a better way to solve this? The question asked to rotate the elements in-place if possible, but I am not sure how to accomplish this at the moment.
javascript algorithm interview-questions
$endgroup$
add a comment |
$begingroup$
I have a working solution for this problem that was accepted in LeetCode:
Given an array, rotate the array to the right by k steps, where k is non-negative."
function rotate(nums, k) {
for (let i = 1; i <= k; i += 1) {
const poppedNum = nums.pop();
nums.unshift(poppedNum);
}
return nums;
}
// rotate([1, 2, 3, 4, 5, 6, 7], 3) -> [5, 6, 7, 1, 2, 3, 4]
Questions:
Would the time complexity be $O(k)$ and space complexity be $O(1)$?
Is there a better way to solve this? The question asked to rotate the elements in-place if possible, but I am not sure how to accomplish this at the moment.
javascript algorithm interview-questions
$endgroup$
add a comment |
$begingroup$
I have a working solution for this problem that was accepted in LeetCode:
Given an array, rotate the array to the right by k steps, where k is non-negative."
function rotate(nums, k) {
for (let i = 1; i <= k; i += 1) {
const poppedNum = nums.pop();
nums.unshift(poppedNum);
}
return nums;
}
// rotate([1, 2, 3, 4, 5, 6, 7], 3) -> [5, 6, 7, 1, 2, 3, 4]
Questions:
Would the time complexity be $O(k)$ and space complexity be $O(1)$?
Is there a better way to solve this? The question asked to rotate the elements in-place if possible, but I am not sure how to accomplish this at the moment.
javascript algorithm interview-questions
$endgroup$
I have a working solution for this problem that was accepted in LeetCode:
Given an array, rotate the array to the right by k steps, where k is non-negative."
function rotate(nums, k) {
for (let i = 1; i <= k; i += 1) {
const poppedNum = nums.pop();
nums.unshift(poppedNum);
}
return nums;
}
// rotate([1, 2, 3, 4, 5, 6, 7], 3) -> [5, 6, 7, 1, 2, 3, 4]
Questions:
Would the time complexity be $O(k)$ and space complexity be $O(1)$?
Is there a better way to solve this? The question asked to rotate the elements in-place if possible, but I am not sure how to accomplish this at the moment.
javascript algorithm interview-questions
javascript algorithm interview-questions
edited Dec 30 '18 at 0:00
Jamal♦
30.3k11119227
30.3k11119227
asked Dec 28 '18 at 23:30
davidattheparkdavidatthepark
1313
1313
add a comment |
add a comment |
3 Answers
3
active
oldest
votes
$begingroup$
Review
Not bad, but there is room for a few improvements to avoid some possible problematic input arguments.
Style
Some points on your code.
- The names
nums
andk
could be better, maybearray
androtateBy
- Try to avoid one use variables unless it makes the lines using them to long. Thus you can pop and unshift in one line
nums.unshift(nums.pop());
- Idiomatic javascript uses zero based loop counters rather than starting at 1. Thus the loop would be
for (let i = 0; i < k; i++) {
Complexity
Your complexity is $O(n)$ and storage $O(1)$ where $n$ is the number of rotations, the range $n>0$
However consider the next examples
rotate([1,2,3,4,5,6,7,8,9], 18); // Will rotate 18 times same as rotate 0
rotate([1,2,3,4,5,6,7,8,9], 8); // Will rotate 8 times same as rotate left 1
rotate([1], 8e9); // Will spend a lot of time not changing anything
Your function will do too much work if the rotations are outside the expected ranges, the rotation can be done in reverse in less steps, or rotating has no effect.
You can limit the complexity to $O(k)$ where $0<k<=n/2$
Rewrite
This is a slight improvement on your function to ensure you don't rotate more than needed.
function rotate(array, rotateBy) {
rotateBy %= array.length;
if (rotateBy < array.length - rotateBy) {
while (rotateBy--) { array.unshift(array.pop()) }
} else {
rotateBy = array.length - rotateBy;
while (rotateBy--) { array.push(array.shift()) }
}
return array;
}
Update
As vnp's answer points out the complexity of Array.unshift
and Array.shift
is not as simple as $O(1)$ and will depend on the array type. We can assume the best case for this problem, sparse array (effectively a hash table) and thus will grow/shrink down at $O(1)$
In that case the function above has a mean complexity of $O(log(n))$ of all possible values of $k$
Note that if the cost of grow/shrink operations is $O(n)$ (dense array) this will add $n$ operations for each $k$ making the above $O(kn)$ with $0<=k<(n/2)$ Expressed in terms of $n$ or $k$ only it remains $O(log(n))$ or $O(k)$
You could also use Array.splice
array.unshift(...array.splice(-rotateBy,rotateBy));
However under the hood the complexity would be a little greater $O(2n)$ (which is still $O(n)$) as splice steps over each item to remove and add to a new array. Then ...
steps over them again to unshift
each item to the array.
The storage would also increase as the spliced array is held in memory until the code has finished unshifting them making storage $O(n)$
If the array contained all the same values the rotation would have no effect thus all rotation could be done in O(1). However there is no way to know this without checking each item in turn.
$endgroup$
add a comment |
$begingroup$
The time complexity really depends on the unshift
time complexity. ECMA does not specify it, but I would expect that it is not constant. I would not be surprised if it is actually linear in the size of the array. If so, the complexity of rotate
would be $O(nk)$.
In fact, I tested the performance of your code by timing your rotate
by 1, for arrays of size from 100000
to 100000000
, doubling the size every time. The results (in milliseconds) are
1, 3, 8, 14, 29, 33, 69, 229, 447, 926
I did few runs, the exact numbers were different, but consistent. You can see that as size doubles the run time (at least) doubles as well.
And again, it was rotation by 1. Rotation by k
will take proportionally longer.
There are few classic algorithms which perform the rotation in true $O(n)$ complexity, that is their execution time does not depend on k
. One is extremely simple to code, but takes effort to comprehend. In pseudocode:
reverse(0, k)
reverse(k, n)
reverse(0, n)
Notice that each element is moved twice. This is suboptimal. Another algorithm moves each element exactly once - right into the place where it belongs. I don't want to spoil the fun of discovering it. Try to figure it out (hint: it needs to compute gcd(n, k)
).
That said, the leetcode problem only asks to print the rotated array. I would seriously consider to not actually perform rotation, but
print values from n-k to n
print values from 0 to n-k
It feels like cheating, but in fact it is valid, and sometimes very useful technique.
$endgroup$
$begingroup$
JS has two types of arrays, sparse and dense. There is no programmatic way to determine what an arrays type is. Sparse arrays use a cheap hash to locate items and thus would not suffer from the need tomemcpy
all items up/down one position when callingArray.unshift
orArray.shift
It is generally assumed that these operations are O(1). Array memory allocations are never single items but rather double array size, or wait and half. Using this scheme does make it possible to have an array that can grow in both directions at O(1). Whether this is in fact done I do not know.
$endgroup$
– Blindman67
Dec 29 '18 at 8:34
add a comment |
$begingroup$
Think twice before modifying function parameters
The posted code modifies the content of the input array nums
and also returns it.
When a function returns something,
it can be confusing if it also modifies the input — a side effect.
Modifying the input and not returning anything (void
in other programming languages) is not confusing.
I think it's important to make a conscious of choice between two non-confusing behaviors: either modify the input and return nothing, or not modify the input and return the result as a new object.
Insert k
items at the start of an array in one step instead of one item in k
steps
In most programming languages, and I think in the context of this exercise, "array" usually means a contiguous area of memory. Inserting an element at the start is implemented by copying existing elements to shift them in memory by 1 position.
For this reason, for a solution to the array rotation problem, I would avoid repeatedly inserting elements at the start of the array, to avoid repeated copying of elements. Even if arrays in JavaScript may behave differently, I would prefer a solution that doesn't raise questions about the potential underlying implementation of arrays, and looks objectively easier to accept.
That is, I would prefer to store k
values in temporary storage, shift the other elements by k
steps (iterating over them only once), and then copy back the k
elements to the correct positions.
function rotate(nums, k) {
k %= nums.length;
const rotated = new Array(nums.length);
for (let i = 0; i < k; i++) {
rotated[i] = nums[nums.length - k + i];
}
for (let i = 0; i < nums.length - k; i++) {
rotated[i + k] = nums[i];
}
return rotated;
}
This is $O(n)$ in both time and space.
The extra space is needed to avoid modifying the input.
If we preferred modifying the input,
then only $O(k)$ extra space would be needed,
for temporary storage of elements that would be overwritten.
Taking advantage of JavaScript's features
Slightly more expensive in terms of time and space,
but my preferred solution would make better use of the built-in functions on arrays in JavaScript than the version in the previous section,
in much more compact code:
function rotate(nums, k) {
k %= nums.length;
const m = nums.length - k;
return nums.slice(m).concat(nums.slice(0, m));
}
$endgroup$
$begingroup$
Returned reference allows chaining. Caller can shallowrotate([...array], 1)
Shallow copy within function means all outside references need updating, the caller may not be able to. Compareconst rotate= (k, n) => (k %= n.length ? n.unshift(...n.splice(-k, k)) : 0, n)
to your last "compact" function, its worst is 50% faster than your best and its best is an order of mag (1700%+) faster than your best (not counting your funcs GC overhead). Google now rank pages in part on load speed, poor performing JS will cost rank points. The days of functional JS are finally over YA.
$endgroup$
– Blindman67
Dec 30 '18 at 12:01
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210530%2fleetcode-189-rotate-array%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Review
Not bad, but there is room for a few improvements to avoid some possible problematic input arguments.
Style
Some points on your code.
- The names
nums
andk
could be better, maybearray
androtateBy
- Try to avoid one use variables unless it makes the lines using them to long. Thus you can pop and unshift in one line
nums.unshift(nums.pop());
- Idiomatic javascript uses zero based loop counters rather than starting at 1. Thus the loop would be
for (let i = 0; i < k; i++) {
Complexity
Your complexity is $O(n)$ and storage $O(1)$ where $n$ is the number of rotations, the range $n>0$
However consider the next examples
rotate([1,2,3,4,5,6,7,8,9], 18); // Will rotate 18 times same as rotate 0
rotate([1,2,3,4,5,6,7,8,9], 8); // Will rotate 8 times same as rotate left 1
rotate([1], 8e9); // Will spend a lot of time not changing anything
Your function will do too much work if the rotations are outside the expected ranges, the rotation can be done in reverse in less steps, or rotating has no effect.
You can limit the complexity to $O(k)$ where $0<k<=n/2$
Rewrite
This is a slight improvement on your function to ensure you don't rotate more than needed.
function rotate(array, rotateBy) {
rotateBy %= array.length;
if (rotateBy < array.length - rotateBy) {
while (rotateBy--) { array.unshift(array.pop()) }
} else {
rotateBy = array.length - rotateBy;
while (rotateBy--) { array.push(array.shift()) }
}
return array;
}
Update
As vnp's answer points out the complexity of Array.unshift
and Array.shift
is not as simple as $O(1)$ and will depend on the array type. We can assume the best case for this problem, sparse array (effectively a hash table) and thus will grow/shrink down at $O(1)$
In that case the function above has a mean complexity of $O(log(n))$ of all possible values of $k$
Note that if the cost of grow/shrink operations is $O(n)$ (dense array) this will add $n$ operations for each $k$ making the above $O(kn)$ with $0<=k<(n/2)$ Expressed in terms of $n$ or $k$ only it remains $O(log(n))$ or $O(k)$
You could also use Array.splice
array.unshift(...array.splice(-rotateBy,rotateBy));
However under the hood the complexity would be a little greater $O(2n)$ (which is still $O(n)$) as splice steps over each item to remove and add to a new array. Then ...
steps over them again to unshift
each item to the array.
The storage would also increase as the spliced array is held in memory until the code has finished unshifting them making storage $O(n)$
If the array contained all the same values the rotation would have no effect thus all rotation could be done in O(1). However there is no way to know this without checking each item in turn.
$endgroup$
add a comment |
$begingroup$
Review
Not bad, but there is room for a few improvements to avoid some possible problematic input arguments.
Style
Some points on your code.
- The names
nums
andk
could be better, maybearray
androtateBy
- Try to avoid one use variables unless it makes the lines using them to long. Thus you can pop and unshift in one line
nums.unshift(nums.pop());
- Idiomatic javascript uses zero based loop counters rather than starting at 1. Thus the loop would be
for (let i = 0; i < k; i++) {
Complexity
Your complexity is $O(n)$ and storage $O(1)$ where $n$ is the number of rotations, the range $n>0$
However consider the next examples
rotate([1,2,3,4,5,6,7,8,9], 18); // Will rotate 18 times same as rotate 0
rotate([1,2,3,4,5,6,7,8,9], 8); // Will rotate 8 times same as rotate left 1
rotate([1], 8e9); // Will spend a lot of time not changing anything
Your function will do too much work if the rotations are outside the expected ranges, the rotation can be done in reverse in less steps, or rotating has no effect.
You can limit the complexity to $O(k)$ where $0<k<=n/2$
Rewrite
This is a slight improvement on your function to ensure you don't rotate more than needed.
function rotate(array, rotateBy) {
rotateBy %= array.length;
if (rotateBy < array.length - rotateBy) {
while (rotateBy--) { array.unshift(array.pop()) }
} else {
rotateBy = array.length - rotateBy;
while (rotateBy--) { array.push(array.shift()) }
}
return array;
}
Update
As vnp's answer points out the complexity of Array.unshift
and Array.shift
is not as simple as $O(1)$ and will depend on the array type. We can assume the best case for this problem, sparse array (effectively a hash table) and thus will grow/shrink down at $O(1)$
In that case the function above has a mean complexity of $O(log(n))$ of all possible values of $k$
Note that if the cost of grow/shrink operations is $O(n)$ (dense array) this will add $n$ operations for each $k$ making the above $O(kn)$ with $0<=k<(n/2)$ Expressed in terms of $n$ or $k$ only it remains $O(log(n))$ or $O(k)$
You could also use Array.splice
array.unshift(...array.splice(-rotateBy,rotateBy));
However under the hood the complexity would be a little greater $O(2n)$ (which is still $O(n)$) as splice steps over each item to remove and add to a new array. Then ...
steps over them again to unshift
each item to the array.
The storage would also increase as the spliced array is held in memory until the code has finished unshifting them making storage $O(n)$
If the array contained all the same values the rotation would have no effect thus all rotation could be done in O(1). However there is no way to know this without checking each item in turn.
$endgroup$
add a comment |
$begingroup$
Review
Not bad, but there is room for a few improvements to avoid some possible problematic input arguments.
Style
Some points on your code.
- The names
nums
andk
could be better, maybearray
androtateBy
- Try to avoid one use variables unless it makes the lines using them to long. Thus you can pop and unshift in one line
nums.unshift(nums.pop());
- Idiomatic javascript uses zero based loop counters rather than starting at 1. Thus the loop would be
for (let i = 0; i < k; i++) {
Complexity
Your complexity is $O(n)$ and storage $O(1)$ where $n$ is the number of rotations, the range $n>0$
However consider the next examples
rotate([1,2,3,4,5,6,7,8,9], 18); // Will rotate 18 times same as rotate 0
rotate([1,2,3,4,5,6,7,8,9], 8); // Will rotate 8 times same as rotate left 1
rotate([1], 8e9); // Will spend a lot of time not changing anything
Your function will do too much work if the rotations are outside the expected ranges, the rotation can be done in reverse in less steps, or rotating has no effect.
You can limit the complexity to $O(k)$ where $0<k<=n/2$
Rewrite
This is a slight improvement on your function to ensure you don't rotate more than needed.
function rotate(array, rotateBy) {
rotateBy %= array.length;
if (rotateBy < array.length - rotateBy) {
while (rotateBy--) { array.unshift(array.pop()) }
} else {
rotateBy = array.length - rotateBy;
while (rotateBy--) { array.push(array.shift()) }
}
return array;
}
Update
As vnp's answer points out the complexity of Array.unshift
and Array.shift
is not as simple as $O(1)$ and will depend on the array type. We can assume the best case for this problem, sparse array (effectively a hash table) and thus will grow/shrink down at $O(1)$
In that case the function above has a mean complexity of $O(log(n))$ of all possible values of $k$
Note that if the cost of grow/shrink operations is $O(n)$ (dense array) this will add $n$ operations for each $k$ making the above $O(kn)$ with $0<=k<(n/2)$ Expressed in terms of $n$ or $k$ only it remains $O(log(n))$ or $O(k)$
You could also use Array.splice
array.unshift(...array.splice(-rotateBy,rotateBy));
However under the hood the complexity would be a little greater $O(2n)$ (which is still $O(n)$) as splice steps over each item to remove and add to a new array. Then ...
steps over them again to unshift
each item to the array.
The storage would also increase as the spliced array is held in memory until the code has finished unshifting them making storage $O(n)$
If the array contained all the same values the rotation would have no effect thus all rotation could be done in O(1). However there is no way to know this without checking each item in turn.
$endgroup$
Review
Not bad, but there is room for a few improvements to avoid some possible problematic input arguments.
Style
Some points on your code.
- The names
nums
andk
could be better, maybearray
androtateBy
- Try to avoid one use variables unless it makes the lines using them to long. Thus you can pop and unshift in one line
nums.unshift(nums.pop());
- Idiomatic javascript uses zero based loop counters rather than starting at 1. Thus the loop would be
for (let i = 0; i < k; i++) {
Complexity
Your complexity is $O(n)$ and storage $O(1)$ where $n$ is the number of rotations, the range $n>0$
However consider the next examples
rotate([1,2,3,4,5,6,7,8,9], 18); // Will rotate 18 times same as rotate 0
rotate([1,2,3,4,5,6,7,8,9], 8); // Will rotate 8 times same as rotate left 1
rotate([1], 8e9); // Will spend a lot of time not changing anything
Your function will do too much work if the rotations are outside the expected ranges, the rotation can be done in reverse in less steps, or rotating has no effect.
You can limit the complexity to $O(k)$ where $0<k<=n/2$
Rewrite
This is a slight improvement on your function to ensure you don't rotate more than needed.
function rotate(array, rotateBy) {
rotateBy %= array.length;
if (rotateBy < array.length - rotateBy) {
while (rotateBy--) { array.unshift(array.pop()) }
} else {
rotateBy = array.length - rotateBy;
while (rotateBy--) { array.push(array.shift()) }
}
return array;
}
Update
As vnp's answer points out the complexity of Array.unshift
and Array.shift
is not as simple as $O(1)$ and will depend on the array type. We can assume the best case for this problem, sparse array (effectively a hash table) and thus will grow/shrink down at $O(1)$
In that case the function above has a mean complexity of $O(log(n))$ of all possible values of $k$
Note that if the cost of grow/shrink operations is $O(n)$ (dense array) this will add $n$ operations for each $k$ making the above $O(kn)$ with $0<=k<(n/2)$ Expressed in terms of $n$ or $k$ only it remains $O(log(n))$ or $O(k)$
You could also use Array.splice
array.unshift(...array.splice(-rotateBy,rotateBy));
However under the hood the complexity would be a little greater $O(2n)$ (which is still $O(n)$) as splice steps over each item to remove and add to a new array. Then ...
steps over them again to unshift
each item to the array.
The storage would also increase as the spliced array is held in memory until the code has finished unshifting them making storage $O(n)$
If the array contained all the same values the rotation would have no effect thus all rotation could be done in O(1). However there is no way to know this without checking each item in turn.
edited Dec 29 '18 at 9:05
answered Dec 29 '18 at 0:33
Blindman67Blindman67
7,9461521
7,9461521
add a comment |
add a comment |
$begingroup$
The time complexity really depends on the unshift
time complexity. ECMA does not specify it, but I would expect that it is not constant. I would not be surprised if it is actually linear in the size of the array. If so, the complexity of rotate
would be $O(nk)$.
In fact, I tested the performance of your code by timing your rotate
by 1, for arrays of size from 100000
to 100000000
, doubling the size every time. The results (in milliseconds) are
1, 3, 8, 14, 29, 33, 69, 229, 447, 926
I did few runs, the exact numbers were different, but consistent. You can see that as size doubles the run time (at least) doubles as well.
And again, it was rotation by 1. Rotation by k
will take proportionally longer.
There are few classic algorithms which perform the rotation in true $O(n)$ complexity, that is their execution time does not depend on k
. One is extremely simple to code, but takes effort to comprehend. In pseudocode:
reverse(0, k)
reverse(k, n)
reverse(0, n)
Notice that each element is moved twice. This is suboptimal. Another algorithm moves each element exactly once - right into the place where it belongs. I don't want to spoil the fun of discovering it. Try to figure it out (hint: it needs to compute gcd(n, k)
).
That said, the leetcode problem only asks to print the rotated array. I would seriously consider to not actually perform rotation, but
print values from n-k to n
print values from 0 to n-k
It feels like cheating, but in fact it is valid, and sometimes very useful technique.
$endgroup$
$begingroup$
JS has two types of arrays, sparse and dense. There is no programmatic way to determine what an arrays type is. Sparse arrays use a cheap hash to locate items and thus would not suffer from the need tomemcpy
all items up/down one position when callingArray.unshift
orArray.shift
It is generally assumed that these operations are O(1). Array memory allocations are never single items but rather double array size, or wait and half. Using this scheme does make it possible to have an array that can grow in both directions at O(1). Whether this is in fact done I do not know.
$endgroup$
– Blindman67
Dec 29 '18 at 8:34
add a comment |
$begingroup$
The time complexity really depends on the unshift
time complexity. ECMA does not specify it, but I would expect that it is not constant. I would not be surprised if it is actually linear in the size of the array. If so, the complexity of rotate
would be $O(nk)$.
In fact, I tested the performance of your code by timing your rotate
by 1, for arrays of size from 100000
to 100000000
, doubling the size every time. The results (in milliseconds) are
1, 3, 8, 14, 29, 33, 69, 229, 447, 926
I did few runs, the exact numbers were different, but consistent. You can see that as size doubles the run time (at least) doubles as well.
And again, it was rotation by 1. Rotation by k
will take proportionally longer.
There are few classic algorithms which perform the rotation in true $O(n)$ complexity, that is their execution time does not depend on k
. One is extremely simple to code, but takes effort to comprehend. In pseudocode:
reverse(0, k)
reverse(k, n)
reverse(0, n)
Notice that each element is moved twice. This is suboptimal. Another algorithm moves each element exactly once - right into the place where it belongs. I don't want to spoil the fun of discovering it. Try to figure it out (hint: it needs to compute gcd(n, k)
).
That said, the leetcode problem only asks to print the rotated array. I would seriously consider to not actually perform rotation, but
print values from n-k to n
print values from 0 to n-k
It feels like cheating, but in fact it is valid, and sometimes very useful technique.
$endgroup$
$begingroup$
JS has two types of arrays, sparse and dense. There is no programmatic way to determine what an arrays type is. Sparse arrays use a cheap hash to locate items and thus would not suffer from the need tomemcpy
all items up/down one position when callingArray.unshift
orArray.shift
It is generally assumed that these operations are O(1). Array memory allocations are never single items but rather double array size, or wait and half. Using this scheme does make it possible to have an array that can grow in both directions at O(1). Whether this is in fact done I do not know.
$endgroup$
– Blindman67
Dec 29 '18 at 8:34
add a comment |
$begingroup$
The time complexity really depends on the unshift
time complexity. ECMA does not specify it, but I would expect that it is not constant. I would not be surprised if it is actually linear in the size of the array. If so, the complexity of rotate
would be $O(nk)$.
In fact, I tested the performance of your code by timing your rotate
by 1, for arrays of size from 100000
to 100000000
, doubling the size every time. The results (in milliseconds) are
1, 3, 8, 14, 29, 33, 69, 229, 447, 926
I did few runs, the exact numbers were different, but consistent. You can see that as size doubles the run time (at least) doubles as well.
And again, it was rotation by 1. Rotation by k
will take proportionally longer.
There are few classic algorithms which perform the rotation in true $O(n)$ complexity, that is their execution time does not depend on k
. One is extremely simple to code, but takes effort to comprehend. In pseudocode:
reverse(0, k)
reverse(k, n)
reverse(0, n)
Notice that each element is moved twice. This is suboptimal. Another algorithm moves each element exactly once - right into the place where it belongs. I don't want to spoil the fun of discovering it. Try to figure it out (hint: it needs to compute gcd(n, k)
).
That said, the leetcode problem only asks to print the rotated array. I would seriously consider to not actually perform rotation, but
print values from n-k to n
print values from 0 to n-k
It feels like cheating, but in fact it is valid, and sometimes very useful technique.
$endgroup$
The time complexity really depends on the unshift
time complexity. ECMA does not specify it, but I would expect that it is not constant. I would not be surprised if it is actually linear in the size of the array. If so, the complexity of rotate
would be $O(nk)$.
In fact, I tested the performance of your code by timing your rotate
by 1, for arrays of size from 100000
to 100000000
, doubling the size every time. The results (in milliseconds) are
1, 3, 8, 14, 29, 33, 69, 229, 447, 926
I did few runs, the exact numbers were different, but consistent. You can see that as size doubles the run time (at least) doubles as well.
And again, it was rotation by 1. Rotation by k
will take proportionally longer.
There are few classic algorithms which perform the rotation in true $O(n)$ complexity, that is their execution time does not depend on k
. One is extremely simple to code, but takes effort to comprehend. In pseudocode:
reverse(0, k)
reverse(k, n)
reverse(0, n)
Notice that each element is moved twice. This is suboptimal. Another algorithm moves each element exactly once - right into the place where it belongs. I don't want to spoil the fun of discovering it. Try to figure it out (hint: it needs to compute gcd(n, k)
).
That said, the leetcode problem only asks to print the rotated array. I would seriously consider to not actually perform rotation, but
print values from n-k to n
print values from 0 to n-k
It feels like cheating, but in fact it is valid, and sometimes very useful technique.
edited Dec 29 '18 at 13:41
mdfst13
17.8k62157
17.8k62157
answered Dec 29 '18 at 2:32
vnpvnp
39.7k232101
39.7k232101
$begingroup$
JS has two types of arrays, sparse and dense. There is no programmatic way to determine what an arrays type is. Sparse arrays use a cheap hash to locate items and thus would not suffer from the need tomemcpy
all items up/down one position when callingArray.unshift
orArray.shift
It is generally assumed that these operations are O(1). Array memory allocations are never single items but rather double array size, or wait and half. Using this scheme does make it possible to have an array that can grow in both directions at O(1). Whether this is in fact done I do not know.
$endgroup$
– Blindman67
Dec 29 '18 at 8:34
add a comment |
$begingroup$
JS has two types of arrays, sparse and dense. There is no programmatic way to determine what an arrays type is. Sparse arrays use a cheap hash to locate items and thus would not suffer from the need tomemcpy
all items up/down one position when callingArray.unshift
orArray.shift
It is generally assumed that these operations are O(1). Array memory allocations are never single items but rather double array size, or wait and half. Using this scheme does make it possible to have an array that can grow in both directions at O(1). Whether this is in fact done I do not know.
$endgroup$
– Blindman67
Dec 29 '18 at 8:34
$begingroup$
JS has two types of arrays, sparse and dense. There is no programmatic way to determine what an arrays type is. Sparse arrays use a cheap hash to locate items and thus would not suffer from the need to
memcpy
all items up/down one position when calling Array.unshift
or Array.shift
It is generally assumed that these operations are O(1). Array memory allocations are never single items but rather double array size, or wait and half. Using this scheme does make it possible to have an array that can grow in both directions at O(1). Whether this is in fact done I do not know.$endgroup$
– Blindman67
Dec 29 '18 at 8:34
$begingroup$
JS has two types of arrays, sparse and dense. There is no programmatic way to determine what an arrays type is. Sparse arrays use a cheap hash to locate items and thus would not suffer from the need to
memcpy
all items up/down one position when calling Array.unshift
or Array.shift
It is generally assumed that these operations are O(1). Array memory allocations are never single items but rather double array size, or wait and half. Using this scheme does make it possible to have an array that can grow in both directions at O(1). Whether this is in fact done I do not know.$endgroup$
– Blindman67
Dec 29 '18 at 8:34
add a comment |
$begingroup$
Think twice before modifying function parameters
The posted code modifies the content of the input array nums
and also returns it.
When a function returns something,
it can be confusing if it also modifies the input — a side effect.
Modifying the input and not returning anything (void
in other programming languages) is not confusing.
I think it's important to make a conscious of choice between two non-confusing behaviors: either modify the input and return nothing, or not modify the input and return the result as a new object.
Insert k
items at the start of an array in one step instead of one item in k
steps
In most programming languages, and I think in the context of this exercise, "array" usually means a contiguous area of memory. Inserting an element at the start is implemented by copying existing elements to shift them in memory by 1 position.
For this reason, for a solution to the array rotation problem, I would avoid repeatedly inserting elements at the start of the array, to avoid repeated copying of elements. Even if arrays in JavaScript may behave differently, I would prefer a solution that doesn't raise questions about the potential underlying implementation of arrays, and looks objectively easier to accept.
That is, I would prefer to store k
values in temporary storage, shift the other elements by k
steps (iterating over them only once), and then copy back the k
elements to the correct positions.
function rotate(nums, k) {
k %= nums.length;
const rotated = new Array(nums.length);
for (let i = 0; i < k; i++) {
rotated[i] = nums[nums.length - k + i];
}
for (let i = 0; i < nums.length - k; i++) {
rotated[i + k] = nums[i];
}
return rotated;
}
This is $O(n)$ in both time and space.
The extra space is needed to avoid modifying the input.
If we preferred modifying the input,
then only $O(k)$ extra space would be needed,
for temporary storage of elements that would be overwritten.
Taking advantage of JavaScript's features
Slightly more expensive in terms of time and space,
but my preferred solution would make better use of the built-in functions on arrays in JavaScript than the version in the previous section,
in much more compact code:
function rotate(nums, k) {
k %= nums.length;
const m = nums.length - k;
return nums.slice(m).concat(nums.slice(0, m));
}
$endgroup$
$begingroup$
Returned reference allows chaining. Caller can shallowrotate([...array], 1)
Shallow copy within function means all outside references need updating, the caller may not be able to. Compareconst rotate= (k, n) => (k %= n.length ? n.unshift(...n.splice(-k, k)) : 0, n)
to your last "compact" function, its worst is 50% faster than your best and its best is an order of mag (1700%+) faster than your best (not counting your funcs GC overhead). Google now rank pages in part on load speed, poor performing JS will cost rank points. The days of functional JS are finally over YA.
$endgroup$
– Blindman67
Dec 30 '18 at 12:01
add a comment |
$begingroup$
Think twice before modifying function parameters
The posted code modifies the content of the input array nums
and also returns it.
When a function returns something,
it can be confusing if it also modifies the input — a side effect.
Modifying the input and not returning anything (void
in other programming languages) is not confusing.
I think it's important to make a conscious of choice between two non-confusing behaviors: either modify the input and return nothing, or not modify the input and return the result as a new object.
Insert k
items at the start of an array in one step instead of one item in k
steps
In most programming languages, and I think in the context of this exercise, "array" usually means a contiguous area of memory. Inserting an element at the start is implemented by copying existing elements to shift them in memory by 1 position.
For this reason, for a solution to the array rotation problem, I would avoid repeatedly inserting elements at the start of the array, to avoid repeated copying of elements. Even if arrays in JavaScript may behave differently, I would prefer a solution that doesn't raise questions about the potential underlying implementation of arrays, and looks objectively easier to accept.
That is, I would prefer to store k
values in temporary storage, shift the other elements by k
steps (iterating over them only once), and then copy back the k
elements to the correct positions.
function rotate(nums, k) {
k %= nums.length;
const rotated = new Array(nums.length);
for (let i = 0; i < k; i++) {
rotated[i] = nums[nums.length - k + i];
}
for (let i = 0; i < nums.length - k; i++) {
rotated[i + k] = nums[i];
}
return rotated;
}
This is $O(n)$ in both time and space.
The extra space is needed to avoid modifying the input.
If we preferred modifying the input,
then only $O(k)$ extra space would be needed,
for temporary storage of elements that would be overwritten.
Taking advantage of JavaScript's features
Slightly more expensive in terms of time and space,
but my preferred solution would make better use of the built-in functions on arrays in JavaScript than the version in the previous section,
in much more compact code:
function rotate(nums, k) {
k %= nums.length;
const m = nums.length - k;
return nums.slice(m).concat(nums.slice(0, m));
}
$endgroup$
$begingroup$
Returned reference allows chaining. Caller can shallowrotate([...array], 1)
Shallow copy within function means all outside references need updating, the caller may not be able to. Compareconst rotate= (k, n) => (k %= n.length ? n.unshift(...n.splice(-k, k)) : 0, n)
to your last "compact" function, its worst is 50% faster than your best and its best is an order of mag (1700%+) faster than your best (not counting your funcs GC overhead). Google now rank pages in part on load speed, poor performing JS will cost rank points. The days of functional JS are finally over YA.
$endgroup$
– Blindman67
Dec 30 '18 at 12:01
add a comment |
$begingroup$
Think twice before modifying function parameters
The posted code modifies the content of the input array nums
and also returns it.
When a function returns something,
it can be confusing if it also modifies the input — a side effect.
Modifying the input and not returning anything (void
in other programming languages) is not confusing.
I think it's important to make a conscious of choice between two non-confusing behaviors: either modify the input and return nothing, or not modify the input and return the result as a new object.
Insert k
items at the start of an array in one step instead of one item in k
steps
In most programming languages, and I think in the context of this exercise, "array" usually means a contiguous area of memory. Inserting an element at the start is implemented by copying existing elements to shift them in memory by 1 position.
For this reason, for a solution to the array rotation problem, I would avoid repeatedly inserting elements at the start of the array, to avoid repeated copying of elements. Even if arrays in JavaScript may behave differently, I would prefer a solution that doesn't raise questions about the potential underlying implementation of arrays, and looks objectively easier to accept.
That is, I would prefer to store k
values in temporary storage, shift the other elements by k
steps (iterating over them only once), and then copy back the k
elements to the correct positions.
function rotate(nums, k) {
k %= nums.length;
const rotated = new Array(nums.length);
for (let i = 0; i < k; i++) {
rotated[i] = nums[nums.length - k + i];
}
for (let i = 0; i < nums.length - k; i++) {
rotated[i + k] = nums[i];
}
return rotated;
}
This is $O(n)$ in both time and space.
The extra space is needed to avoid modifying the input.
If we preferred modifying the input,
then only $O(k)$ extra space would be needed,
for temporary storage of elements that would be overwritten.
Taking advantage of JavaScript's features
Slightly more expensive in terms of time and space,
but my preferred solution would make better use of the built-in functions on arrays in JavaScript than the version in the previous section,
in much more compact code:
function rotate(nums, k) {
k %= nums.length;
const m = nums.length - k;
return nums.slice(m).concat(nums.slice(0, m));
}
$endgroup$
Think twice before modifying function parameters
The posted code modifies the content of the input array nums
and also returns it.
When a function returns something,
it can be confusing if it also modifies the input — a side effect.
Modifying the input and not returning anything (void
in other programming languages) is not confusing.
I think it's important to make a conscious of choice between two non-confusing behaviors: either modify the input and return nothing, or not modify the input and return the result as a new object.
Insert k
items at the start of an array in one step instead of one item in k
steps
In most programming languages, and I think in the context of this exercise, "array" usually means a contiguous area of memory. Inserting an element at the start is implemented by copying existing elements to shift them in memory by 1 position.
For this reason, for a solution to the array rotation problem, I would avoid repeatedly inserting elements at the start of the array, to avoid repeated copying of elements. Even if arrays in JavaScript may behave differently, I would prefer a solution that doesn't raise questions about the potential underlying implementation of arrays, and looks objectively easier to accept.
That is, I would prefer to store k
values in temporary storage, shift the other elements by k
steps (iterating over them only once), and then copy back the k
elements to the correct positions.
function rotate(nums, k) {
k %= nums.length;
const rotated = new Array(nums.length);
for (let i = 0; i < k; i++) {
rotated[i] = nums[nums.length - k + i];
}
for (let i = 0; i < nums.length - k; i++) {
rotated[i + k] = nums[i];
}
return rotated;
}
This is $O(n)$ in both time and space.
The extra space is needed to avoid modifying the input.
If we preferred modifying the input,
then only $O(k)$ extra space would be needed,
for temporary storage of elements that would be overwritten.
Taking advantage of JavaScript's features
Slightly more expensive in terms of time and space,
but my preferred solution would make better use of the built-in functions on arrays in JavaScript than the version in the previous section,
in much more compact code:
function rotate(nums, k) {
k %= nums.length;
const m = nums.length - k;
return nums.slice(m).concat(nums.slice(0, m));
}
edited Dec 30 '18 at 7:07
answered Dec 29 '18 at 16:08
janosjanos
97.8k12125350
97.8k12125350
$begingroup$
Returned reference allows chaining. Caller can shallowrotate([...array], 1)
Shallow copy within function means all outside references need updating, the caller may not be able to. Compareconst rotate= (k, n) => (k %= n.length ? n.unshift(...n.splice(-k, k)) : 0, n)
to your last "compact" function, its worst is 50% faster than your best and its best is an order of mag (1700%+) faster than your best (not counting your funcs GC overhead). Google now rank pages in part on load speed, poor performing JS will cost rank points. The days of functional JS are finally over YA.
$endgroup$
– Blindman67
Dec 30 '18 at 12:01
add a comment |
$begingroup$
Returned reference allows chaining. Caller can shallowrotate([...array], 1)
Shallow copy within function means all outside references need updating, the caller may not be able to. Compareconst rotate= (k, n) => (k %= n.length ? n.unshift(...n.splice(-k, k)) : 0, n)
to your last "compact" function, its worst is 50% faster than your best and its best is an order of mag (1700%+) faster than your best (not counting your funcs GC overhead). Google now rank pages in part on load speed, poor performing JS will cost rank points. The days of functional JS are finally over YA.
$endgroup$
– Blindman67
Dec 30 '18 at 12:01
$begingroup$
Returned reference allows chaining. Caller can shallow
rotate([...array], 1)
Shallow copy within function means all outside references need updating, the caller may not be able to. Compare const rotate= (k, n) => (k %= n.length ? n.unshift(...n.splice(-k, k)) : 0, n)
to your last "compact" function, its worst is 50% faster than your best and its best is an order of mag (1700%+) faster than your best (not counting your funcs GC overhead). Google now rank pages in part on load speed, poor performing JS will cost rank points. The days of functional JS are finally over YA.$endgroup$
– Blindman67
Dec 30 '18 at 12:01
$begingroup$
Returned reference allows chaining. Caller can shallow
rotate([...array], 1)
Shallow copy within function means all outside references need updating, the caller may not be able to. Compare const rotate= (k, n) => (k %= n.length ? n.unshift(...n.splice(-k, k)) : 0, n)
to your last "compact" function, its worst is 50% faster than your best and its best is an order of mag (1700%+) faster than your best (not counting your funcs GC overhead). Google now rank pages in part on load speed, poor performing JS will cost rank points. The days of functional JS are finally over YA.$endgroup$
– Blindman67
Dec 30 '18 at 12:01
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210530%2fleetcode-189-rotate-array%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown