LeetCode 189 - Rotate Array












5












$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.











share|improve this question











$endgroup$

















    5












    $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.











    share|improve this question











    $endgroup$















      5












      5








      5





      $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.











      share|improve this question











      $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






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Dec 30 '18 at 0:00









      Jamal

      30.3k11119227




      30.3k11119227










      asked Dec 28 '18 at 23:30









      davidattheparkdavidatthepark

      1313




      1313






















          3 Answers
          3






          active

          oldest

          votes


















          3












          $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 and k could be better, maybe array and rotateBy

          • 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.






          share|improve this answer











          $endgroup$





















            3












            $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.






            share|improve this answer











            $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 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



















            1












            $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));
            }





            share|improve this answer











            $endgroup$













            • $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











            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
            });


            }
            });














            draft saved

            draft discarded


















            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









            3












            $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 and k could be better, maybe array and rotateBy

            • 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.






            share|improve this answer











            $endgroup$


















              3












              $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 and k could be better, maybe array and rotateBy

              • 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.






              share|improve this answer











              $endgroup$
















                3












                3








                3





                $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 and k could be better, maybe array and rotateBy

                • 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.






                share|improve this answer











                $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 and k could be better, maybe array and rotateBy

                • 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.







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Dec 29 '18 at 9:05

























                answered Dec 29 '18 at 0:33









                Blindman67Blindman67

                7,9461521




                7,9461521

























                    3












                    $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.






                    share|improve this answer











                    $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 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
















                    3












                    $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.






                    share|improve this answer











                    $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 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














                    3












                    3








                    3





                    $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.






                    share|improve this answer











                    $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.







                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    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 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
















                    $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











                    1












                    $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));
                    }





                    share|improve this answer











                    $endgroup$













                    • $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
















                    1












                    $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));
                    }





                    share|improve this answer











                    $endgroup$













                    • $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














                    1












                    1








                    1





                    $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));
                    }





                    share|improve this answer











                    $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));
                    }






                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    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 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
















                    $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


















                    draft saved

                    draft discarded




















































                    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.




                    draft saved


                    draft discarded














                    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





















































                    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







                    Popular posts from this blog

                    Bressuire

                    Cabo Verde

                    Gyllenstierna