• 算法的时间复杂度分析习题专题


    之前写了一篇重点是讲理论,今天重点在于对于题目的分析

    题目难度不分先后,有题目来源会直接给出链接或者位置

    第一题:消失的数字

    题目来源:LeetCode消失的数字

     分析

    第一种思路分析:

             参考代码:位置(chapter1/c++/disappear-number/lc_disappear_number1.cc)

    1. #include
    2. #include
    3. using namespace std;
    4. int find_disappear(int nums[], int len)
    5. {
    6. int res = -1;//保存结果,-1代表值没有被改变
    7. //先对数组进行排序
    8. sort(nums, nums + len);//需要两个参数,数组头指针与指针,但是这里不包括尾指针,所以指向数组越界的位置
    9. for (int i = 0; i < len; i++)
    10. {
    11. if (i != nums[i])
    12. {
    13. //这里直接缺少的就是i
    14. res = i;
    15. }
    16. }
    17. //这里就是没有匹配到的情况,就是最大的索引
    18. if (res == -1)
    19. {
    20. res = len;
    21. }
    22. return res;
    23. }
    24. int main()
    25. {
    26. int arr[] = {0, 1, 2};
    27. //这里长度要单独拿出来计算,因为arr传入到find函数里面,会直接当成一级指针来进行处理
    28. //指针一般也就是一般windows上4个字节,linux上八个字节来进行处理
    29. int len = sizeof(arr) / sizeof(arr[0]);
    30. int res = find_disappear(arr, len);
    31. printf("缺失的数字是:%d\n", res);
    32. return 0;
    33. }

    时间复杂度为O(nlogn)明显与题目要求不符合,no_pass

    下面是第二种思路分析

    参考代码:位置(chapter1/c++/disappear-number/lc_disappear_number2_pass.cc)

    1. #include
    2. int find_number(int *nums, int len)
    3. {
    4. //正确的长度是
    5. int len_correct = len + 1;
    6. //计算正确求和值
    7. int sum_correct = (len_correct * (len_correct - 1)) / 2;
    8. int sum_wrong = 0;//非正确的求和值
    9. //开始轮替,计算非正确的值,然后相减
    10. for (int i = 0; i < len; i++)
    11. {
    12. sum_wrong += nums[i];//因为i本身就是从0开始的,所以用i来累加求和
    13. }
    14. return sum_correct - sum_wrong;
    15. }
    16. int main()
    17. {
    18. int arr[] = {0, 1, 3};
    19. int res = find_number(arr,3);
    20. printf("%d\n", res);
    21. return 0;
    22. }

    时间度O(n),通过

    下面是第三种思路分析:

     

    参考代码: 位置/chapter1/c++/disappear-number/lc_disappear_number3_pass.cc

    1. #include
    2. 2
    3. 3
    4. 4
    5. 5 int missing_number(int *nums, int len)
    6. 6 {
    7. 7 int res = 0;
    8. 8 for (int i = 0; i < len; i++)
    9. 9 {
    10. 10 res ^= *(nums + i);
    11. 11 res ^= i + 1;
    12. 12 }
    13. 13 return res;
    14. 14 }
    15. 15
    16. 16 int main()
    17. 17 {
    18. 18 int nums[] = {0, 1};
    19. 19 int len = sizeof(nums) / sizeof(nums[0]);
    20. 20 int res = missing_number(nums, len);
    21. 21 printf("%d\n", res);
    22. 22 return 0;
    23. 23 }
    24. ~

    leetcode提交代码c++:

    参考代码:位置(/chapter1/c++/disappear-number/lc_accept.cc)

    1. class Solution {
    2. public:
    3. int missingNumber(std::vector<int>& nums) {
    4. int len_correct = nums.size() + 1;
    5. int sum_correct = (len_correct * (len_correct - 1)) / 2;
    6. int sum_wrong = 0;
    7. for (int i = 0; i < nums.size(); ++i) {
    8. sum_wrong += nums[i];
    9. }
    10. return sum_correct - sum_wrong;
    11. }
    12. };

     

    java代码的实现

    java小知识点补讲:

    1.一个类中的静态方法在new一个非静态成员成员内部类的时候,成员内部类必须是static的  

    也就是说下面这种写法是错误的

    应该换成下面这种写法

     如果你实在想内部类不设置为static的,那么,也可以按照如下方式来做

    说一下原因:main是一个静态方法,也就是说它是先于对象而存在的,如果Solution是一个依赖于Lc对象才存在的对象,那么如果直接new Solution肯定是不行的,因为Lc这个类都没有被加载到内存里面来,也就是说,这个类在内存里面是没有空间的,Lc没有空间,那么它的内部类 Solution凭什么会有空间。另外说一句,类加载到方法区是不一定会去创建这个类的实例的,他只会把类的相关信息封存起来放到一个Class<相关类>的一个字节码对象里面

    说一下上面的类加载到方法区的机制

    参考代码:位置(/chapter1/java/disappear-number/Lc.java)

    1. public class Lc {
    2. static class Solution {
    3. public int missingNumber(int[] nums) {
    4. //先求出正确的和
    5. int correct_len = nums.length + 1;
    6. int correct_sum = correct_len * (correct_len - 1) / 2;
    7. int wrong_sum = 0;//保留不正确的和
    8. for (int i = 0; i < nums.length; i++) {
    9. wrong_sum += nums[i];
    10. }
    11. return correct_sum - wrong_sum;
    12. }
    13. }
    14. public static void main(String []args) {
    15. int[] nums = {3, 0, 1};
    16. Solution solution = new Solution();
    17. int res = solution.missingNumber(nums);
    18. System.out.println(res);
    19. }
    20. }

    java第二种实现方式,采用异或的方式处理

    参考代码:位置(/chapter1/java/disappear-number/Lc1.java) 

    1. class Solution {
    2. 2 public int missingNumber(int[] nums) {
    3. 3 //让所有数据异或
    4. 4 int res = 0;//这用0来异或就是0与任何数异或都是0
    5. 5 int len = nums.length;
    6. 6 for (int i = 0; i < len; i++) {
    7. 7 res ^= nums[i];//先异或原数组元素0 0 1 1 2
    8. 8 res ^= i + 1;//正确数据
    9. 9 }
    10. 10 return res;
    11. 11 }
    12. 12 }
    13. 13
    14. 14
    15. 15 public class Lc1 {
    16. 16 public static void main(String[] args) {
    17. 17 int[] nums = {0, 1};
    18. 18 Solution s = new Solution();
    19. 19 int res = s.missingNumber(nums);
    20. 20 System.out.println("缺失的值是:" + res);
    21. 21 }
    22. 22 }

     上面代码自行提交,具体逻辑分析看上面c++代码分析,

    第二题:轮转数组

    题目来源:leetcode轮转数组 

    第一种思路:把整个数组变成一个循环链表来做

    针对于上面的情况,我们设计一个下面的链表来处理

    另外我需要说一下:链表不一定完全要采用分配内存,指针的方式的来做,可以用一个数组来模拟这样一种数据结构,下面看代码

    参考代码:chapter1/c++/rotate-array/lc_rotate_array1.cc

    1. #include
    2. //定义存放数组下标的节点结构
    3. struct node
    4. {
    5. int pos;//当前索引,我们需要打印的索引
    6. int next;//它指向的下一个结点的地址,我们需要靠它移动的索引
    7. };
    8. void rotate_array(int *nums, int len, int k)
    9. {
    10. //安全性检查
    11. //注意题目中的数组长度是给出了一个具体范围的
    12. if (nums == NULL || len < 1 || len > 100000 || k < 0 || k > 100000)
    13. {
    14. return;
    15. }
    16. node arr[len];
    17. //根据长度来初始化这个索引数组
    18. for (int i = 0; i < len; i++)
    19. {
    20. arr[i].pos = i;
    21. //最后一个下标的next要分开考虑,不能按照当前索引递增
    22. //它是指向索引0这个结点的
    23. if (i == len - 1)
    24. {
    25. arr[i].next = 0;
    26. } else {
    27. arr[i].next = i + 1;
    28. }
    29. }
    30. //下面考虑k是几
    31. //我们要根据k来判定pos指针应该移动到哪一个结点
    32. //然后开时顺时针轮替打印
    33. //首先pos是指在数组最后一个索引位置,这是起始位置
    34. int index = len - 1;//数组中实际的索引长度,用它来控制数组索引的回减
    35. int pos = index;
    36. //i从1开始轮替的原因是如果k = 1,就不需要移动,已经指向了移动1次之后的标准打印位置
    37. for (int i = 1; i < k; i++)
    38. {
    39. //考虑回减到pos = 0的情况,到达头之后
    40. //又要把链表指针指向了尾部
    41. if (pos == 0)
    42. {
    43. //回到尾部,也就是最后一个结点
    44. pos = len - 1;
    45. index = len - 1;//index也回到最后一个位置,以便进行下一次轮替
    46. } else {
    47. //这里注意,不要使用index--
    48. //不然会先把index的值交给pos之后,然后在--
    49. pos = --index;
    50. }
    51. }
    52. //上面确定好了pos的位置,然后我们就可以顺时针打印了
    53. //打印肯定是数组的长度截止
    54. //按照给的格式打印,当然我们也可以把它放到一个新数组
    55. //然后返回打印
    56. printf("[");
    57. for (int i = 0; i < len; i++)//注意len在上面已经变成
    58. {
    59. if (i == len - 1)
    60. {
    61. printf("%d]\n", nums[pos]);//注意打印原数组
    62. } else {
    63. printf("%d,", nums[pos]);
    64. }
    65. //pos按照数组中next往下面移动
    66. pos = arr[pos].next;
    67. }
    68. }
    69. int main()
    70. {
    71. int arr[] = {1,2,3,4,5,6,7};
    72. int len = sizeof(arr) / sizeof(arr[0]);
    73. int k = 3;
    74. rotate_array(arr, len, k);
    75. return 0;
    76. }

    对上面的代码进行一个时间复杂度与空间复杂度的分析

    先来说一下时间复杂度

    综上所述时间复杂度是O(len + k + len),又因为我们在题目中对k与len 都是做了限制的,所以上面的k与len都可以看成一个常数,常数级别的算法时间复杂度就是O(1),但是我如果我们没有对上面的K与Len 进行一个限制,那么这两个数就不是常数,说是时间复杂度将是一个线性的,也就是O(n),注意这里是大O(1)

    下面来分析一下空间复杂度:

    然后其他空间都是常量级别的空间,也就是说不会随着输入的数据规模增加而增加,所以这里的空间复杂度就是O(n + 1),常数项忽略不计,空间复杂度就是O(n) 

    题目的进阶要我们时间复杂度是O(1),很明显这里不太符合要求

    no pass

    第二种思路:还是把整个链表当成一个循环链表来看,只是我们对空间和时间上进行一些优化

    优化1:k的取值 

    所以上面我们对k的取值先做一些优化,减少循环的时间 k %= len

     优化2:额外开辟数组的空间优化

    针对于上面两种优化我们对代码做一些改进

     参考代码:/chapter1/c++/rotate-array/lc_rotate_array2.cc

    1. #include
    2. void rotate_array(int *nums, int len, int k)
    3. {
    4. //安全性检查
    5. //注意题目中的数组长度是给出了一个具体范围的
    6. if (nums == NULL || len < 1 || len > 100000 || k < 0 || k > 100000)
    7. {
    8. return;
    9. }
    10. //一个额外空间的开辟
    11. int next[len];//直接保存下一个节点的索引
    12. //初始化这片额外的空间
    13. for (int i = 0; i < len; i++)
    14. {
    15. if (i == len - 1)
    16. {
    17. next[i] = 0;
    18. }
    19. else
    20. {
    21. next[i] = i + 1;
    22. }
    23. }
    24. //下面对k的取值做一个优化
    25. k %= len;//限制Len的范围里面,毕竟在len之后,就要开始准备轮替了
    26. //但是我们确定我们pos的值之前,我们需要研究一下上面拿到的这样一个k
    27. //的取值是否是合理的取值
    28. //[1,2,3] 我们初始化的指针是指向了3这个位置,如果k=3,那么经过优化之后的k
    29. //就等于0,,也就是说明它需要回到一个起始状态,那我们需要移动这个指针,两次
    30. //回到1这个位置,因为pos这个位置是回减的,其余的当k= 1或者k=2都是合理
    31. //我上面假设的是len=3的时候的情况
    32. if (k == 0)
    33. {
    34. //k等于0的时候就说明
    35. //数组的所有状态全部走完,回到了起点
    36. k = len;
    37. }
    38. //下面开始确定pos的位置
    39. int index = len - 1;//中间轮替的变量,不改变len的取值
    40. int pos = index;//初始化到最后一个位置的指针
    41. for (int i = 1; i < k; i++)
    42. {
    43. if (pos == 0)
    44. {
    45. pos = len - 1;//回到我们设定的起始位置
    46. } else {
    47. pos = (--index);
    48. }
    49. }
    50. //上面pos就是指向了我们想要的位置
    51. //下面开始根据next打印值
    52. //这是一个用索引构造而成的循环链表
    53. printf("[");
    54. for (int j = 0; j < len; j++)
    55. {
    56. printf("%d",nums[pos]);
    57. if (j != len - 1)
    58. {
    59. printf(",");
    60. }
    61. pos = next[pos];
    62. }
    63. printf("]");
    64. }
    65. int main()
    66. {
    67. int arr[] = {1,2,3,4,5,6,7};
    68. int len = sizeof(arr) / sizeof(arr[0]);
    69. int k = 3;
    70. rotate_array(arr, len, k);
    71. return 0;
    72. }

    下面还是来分析一下算法的时间复杂度与空间复杂度

     上面的算法时间复杂度就是:O(len + len + len),由于len本身在题目中是一个常数,所以时间复杂度还是O(1),但是很明显上面的算法在某种情况下来说会快一些,比如我们在K值确定位置的一个轮询的过程中,如果k值设置到最大,len比较小的时候,只需要循环到len就可以了

    下面分析空间复杂度:

    空间复杂度没有改进。从微观层面来说,我们这个算法为每一个额外的数据节点都节省了四个字节大小的额外空间 ,但是由于有一个随着数据规模增大而增大的数组,所以,空间复杂度还是O(n)

    第三种思路:采用双向循环链表思想来优化一思路2的代码

    我们在做第二种思路的时候,也就是单向循环链表的时候,我们会发现其实有几个弊端

    比如下面:

     

    好了,下面直接上代码

    参考代码:/chapter1/c++/rotate-array/lc_rotate_array3.cc

    1. #include
    2. void rotate_array(int *nums, int len, int k)
    3. {
    4. //安全性检查
    5. //注意题目中的数组长度是给出了一个具体范围的
    6. if (nums == NULL || len < 1 || len > 100000 || k < 0 || k > 100000)
    7. {
    8. return;
    9. }
    10. int next[len];
    11. //初始化双向链表数组
    12. for (int i = 0; i < len; i++)
    13. {
    14. //反向循环,在数组里面我们就可以了控制,所以但是单数只需要一个next数组就可以了
    15. if (i == len - 1)
    16. {
    17. next[i] = 0;
    18. } else {
    19. next[i] = i + 1;
    20. }
    21. }
    22. //优化k的值,计算pos的位置
    23. k %= len;//len = 3[0,1,2];//0就代表回到了链表的起点
    24. int pos = 0;//这里我们可以直接把pos放在第一个结点
    25. //这里指针需要逆时针移动,所以不用怕
    26. int index = len;
    27. for (int i = 0; i < k; i++)
    28. {
    29. pos = --index;//比如k = 1的时候就移动到k = 2的位置,k = 2,就会移动到1的位置
    30. if (pos == 0)
    31. {
    32. index = len;//如果回到起始位置,在往下面循环,就要从最后一个位置
    33. //但是这里也不用不限定,上面只会在当前数组里面循环一圈结束,这是k决定的
    34. }
    35. }
    36. //上面就是逆时针移动了指针
    37. //下面我们顺时针打印出来
    38. printf("[");
    39. for (int i = 0; i < len; i++)
    40. {
    41. printf("%d", nums[pos]);
    42. if (i != len - 1)
    43. {
    44. printf(",");
    45. }
    46. pos = next[pos];
    47. }
    48. printf("]");
    49. }
    50. int main()
    51. {
    52. int arr[] = {1,2,3,4,5,6,7};
    53. int len = sizeof(arr) / sizeof(arr[0]);
    54. int k = 3;
    55. rotate_array(arr, len, k);
    56. return 0;
    57. }

    算法时间复杂度与空间复杂度与上面一样,这个位置就不做过多的阐述

    第四种思路:直接采用暴力破解的方法,双备份循环的方法来做

    下面是思路分析

    参考代码:位置(chapter1/c++/rotate-array/lc_rotate_array4.cc)

    1. #include
    2. void rotate_array(int *nums, int len, int k)
    3. {
    4. //安全性检查
    5. //注意题目中的数组长度是给出了一个具体范围的
    6. if (nums == NULL || len < 1 || len > 100000 || k < 0 || k > 100000)
    7. {
    8. return;
    9. }
    10. //还是把k进行优化一下
    11. k %= len;//限制在数组长度范围之内
    12. int bak_cur;//赋值给j+1的结点值
    13. int bak_next;//j+1的备份
    14. //整体循环移动
    15. for (int i = 0; i < k;i++)
    16. {
    17. //初始化bak_cur与bak_next
    18. //这里bak_next初始化什么都无所谓
    19. //主要是需要初始化bak_cur,因为我们要用bak_cur初始化j+1这样一个位置
    20. bak_cur = nums[0];//每次内部数组循环完之后都会回到索引为0的位置开始移动
    21. bak_next = 0;//这里给个初始值就可以了
    22. //开始每一次内部的数组循环移动
    23. for (int j = 0; j < len; j++)
    24. {
    25. //这里最后一个节点需要特殊考虑
    26. //因为它的下一个节点又回到了索引为0的节点
    27. //并且它的备份已经在它之前一个位置就备份好了
    28. //我们也不需要去修改bak_next这个备份值
    29. if (j == len - 1)
    30. {
    31. nums[0] = bak_cur;//直接给备份结点值就可以了
    32. //然后开始下一次k的轮替
    33. } else {
    34. //先来备份j+1
    35. bak_next = nums[j+1];
    36. //在去改变j+1;
    37. nums[j+1] = bak_cur;//用上一个节点的备份去改变
    38. bak_cur = bak_next;//在把上一个节点的备份值给改变一下
    39. }
    40. }
    41. }
    42. //打印
    43. printf("[");
    44. for (int n = 0; n < len; n++)
    45. {
    46. printf("%d", nums[n]);//已经是移动完了之后的数组了
    47. if (n != len - 1)
    48. {
    49. printf(",");
    50. }
    51. }
    52. printf("]");
    53. }
    54. int main()
    55. {
    56. int arr[] = {1,2,3,4,5,6,7};
    57. int len = sizeof(arr) / sizeof(arr[0]);
    58. int k = 3;
    59. rotate_array(arr, len, k);
    60. return 0;
    61. }

    算法时间复杂度与空间复杂度分析

    时间复杂度:

    虽然题目限定了数据大小,可以看成一个常量级别的算法时间复杂度,但是这里采用了双层for循环,时间复杂度直接拉满,虽然题目没有规定时间复杂度,但是这个算法也不用考虑

    空间复杂度:

    整体的算法我们没有随着数据的规模增大而新开辟一个数组,全是常量级别的空间,所以,这里的空间复杂度都是O(1) ,如果只考虑空间复杂度来说,这个题目可以算pass,通过
     

    第五种思路:直接根据每一个结点移动k次之后,算出它相应的被赋值结点的位置索引

    这个题目我们简单思考一下:

    它是有规律性的,比如k值,它总会在循环k次之后,又重新回到初始状态,所以我们可以直接限定k的取值,那么又在考虑每一个结点变化之后的位置,其实也是一个规模性,比如下面这个

    参考代码: 位置(chapter1/c++/rotate-array/lc_rotate_array5.cc)

    1. #include
    2. void rotate_array(int *nums, int len, int k)
    3. {
    4. //做一些安全性判断
    5. if (nums == NULL || len < 1 || len > 100000 || k < 0 || k > 100000)
    6. {
    7. return;
    8. }
    9. //重新定位k的值
    10. //k的值决定了数组形态
    11. //每次到达k=len的时候,又会开始轮替
    12. //也就是说数组会回到新的起始位置
    13. //对于这样一个循环的找位置的数组来说
    14. //采用%len长度,直接限定了它的范围
    15. //所有k轮替全都可以放在len这个范围里面
    16. k %= len;//假如len = 3,k的取值[0,1,2]0代表不动,1代表1次,2代表2次
    17. //k决数组形态,那么数组最终移动之后的形态确定了
    18. //下面开启式确定每一个位置,被哪一个位置所替代
    19. //注意上面的操作都是在一个数组里面操作,只是找相对位置
    20. printf("[");
    21. for (int i = 0; i < len; i++)
    22. {
    23. //计算新位置
    24. //新位置就是数组会被替换的位置
    25. //拿到替换位置,也就是拿到了这个位置应该有的值
    26. //这个位置计算的分析看文章部分
    27. int new_pos = (i + len - k) % len;
    28. printf("%d", nums[new_pos]);
    29. if (i < len - 1)
    30. {
    31. printf(",");
    32. }
    33. }
    34. printf("]\n");
    35. }
    36. int main()
    37. {
    38. int arr[] = {1,2,3,4,5,6,7};
    39. int len = sizeof(arr) / sizeof(arr[0]);
    40. int k = 3;
    41. rotate_array(arr, len, k);
    42. return 0;
    43. }

    算法时间复杂度分析:

    这里几乎完美,没有任何的数据移

    空间复杂度分析:

    O(1) 没有任何任何随着数据规模增大而开辟的空间,完美通过pass

    下面是Java代码的实现 

     下面这个没有位置,因为不可提交,所以这里仅仅是看一下分析思路,因为java里面的代码代码全部都是提交版本,所以这里不做过多说明

    1. #include
    2. void rotate_array(int *nums, int len, int k)
    3. {
    4. //做一些安全性判断
    5. if (nums == NULL || len < 1 || len > 100000 || k < 0 || k > 100000)
    6. {
    7. return;
    8. }
    9. //重新定位k的值
    10. //k的值决定了数组形态
    11. //每次到达k=len的时候,又会开始轮替
    12. //也就是说数组会回到新的起始位置
    13. //对于这样一个循环的找位置的数组来说
    14. //采用%len长度,直接限定了它的范围
    15. //所有k轮替全都可以放在len这个范围里面
    16. k %= len;//假如len = 3,k的取值[0,1,2]0代表不动,1代表1次,2代表2次
    17. //k决数组形态,那么数组最终移动之后的形态确定了
    18. //下面开启式确定每一个位置,被哪一个位置所替代
    19. //注意上面的操作都是在一个数组里面操作,只是找相对位置
    20. printf("[");
    21. for (int i = 0; i < len; i++)
    22. {
    23. //计算新位置
    24. //新位置就是数组会被替换的位置
    25. //拿到替换位置,也就是拿到了这个位置应该有的值
    26. //这个位置计算的分析看文章部分
    27. int new_pos = (i + len - k) % len;
    28. printf("%d", nums[new_pos]);
    29. if (i < len - 1)
    30. {
    31. printf(",");
    32. }
    33. }
    34. printf("]\n");
    35. }
    36. int main()
    37. {
    38. int arr[] = {1,2,3,4,5,6,7};
    39. int len = sizeof(arr) / sizeof(arr[0]);
    40. int k = 3;
    41. rotate_array(arr, len, k);
    42. return 0;
    43. }

    写到这的时候我发现我理解错了题目的意思,这个题目的意思是,需要在函数内部,把这个数组给改变了,所以上面思路的提交代码全都不可用,不过也很好解决,每一种除了第四种思路之外,每一种思路你都弄一个新数组,然后把找到的相应的值放进去就行了,不过全部代码的空间复杂度就会特别大。对于第四种暴力破解,虽然满足了空间上的O(1)开销但是耗费的时间确是非常大,也不建议使用

    下面是第四种方法的java代码实现,不提供位置,仅供参考

    1. import java.util.Arrays;
    2. class Solution {
    3. public void rotate(int []nums, int k) {
    4. if (nums == null || nums.length < 1 || nums.length > 100000 ||
    5. k < 0 || k > 100000) {
    6. return;
    7. }
    8. int len = nums.length;
    9. //优化k
    10. k %= len;
    11. //采用双备份的方法来做
    12. int bak_cur;//给下一个j+1赋值
    13. int bak_next;//j+1的备份
    14. for (int i = 0; i < k; i++)
    15. {
    16. //没走完一次内部循环,又会回到数组开头的位置
    17. bak_cur = nums[0];//这里永远初始化第一个结点的值
    18. bak_next = 0;//这里初始化什么目前来说无所谓
    19. for (int j = 0; j < len; j++)
    20. {
    21. //分尾结点来考虑
    22. if (j != len - 1) {
    23. //先来备份j+1
    24. bak_next = nums[j + 1];
    25. //赋值
    26. nums[j + 1] = bak_cur;
    27. //改变备份当前结点
    28. bak_cur = bak_next;
    29. } else {
    30. //直接把最后一个结点
    31. //就要去改变第一个结点
    32. //然后又回到开始的循环里面
    33. nums[0] = bak_cur;
    34. }
    35. }
    36. }
    37. }
    38. }
    39. public class Lc {
    40. public static void main(String []args) {
    41. Solution s = new Solution();
    42. int []nums = {1,2,3,4,5,6,7};
    43. int k = 3;
    44. s.rotate(nums, k);
    45. System.out.println(Arrays.toString(nums));
    46. }
    47. }

    上面代码可以提交一下,肯定会超时

    好了我们不得不在想一种思路

    第六种思路:考虑采用反转的思想来做 

    首先这个数组的整体移动呈现一个规律性,比如前k个结点往后,后面len - k个结点往前,只是针对于不同的k值,数组最终呈现不同的形态,因此我们可以这样理解,每一个k值都会有一个反转的规律,我们把这个规律记下来就行,举个实际例子来说

    参考代码:位置(/chapter1/java/rotate-array/Lc.java) 

    1. import java.util.Arrays;
    2. class Solution {
    3. void rotate(int []nums, int k) {
    4. if (nums == null || nums.length < 1 || nums.length > 100000 ||
    5. k < 0 || k > 100000) {
    6. return;
    7. }
    8. int len = nums.length;
    9. //还是把k值进行优化
    10. k %= len;
    11. //先来反转整体数组
    12. reverse(nums, 0, len - 1);
    13. //旋转后面前移,旋转前k个元素
    14. reverse(nums, 0, k - 1);
    15. //前面的往后移,旋转后买那len - k个元素
    16. reverse(nums, k, len - 1);
    17. }
    18. //先来写一个反转工具
    19. void reverse(int []nums, int start, int end) {
    20. while(start < end) {
    21. //这里面就是值的交换
    22. int temp = nums[start];
    23. nums[start] = nums[end];
    24. nums[end] = temp;
    25. start++;
    26. end--;
    27. }
    28. }
    29. }
    30. public class Lc {
    31. public static void main(String []args) {
    32. Solution s = new Solution();
    33. int []nums = {1,2,3,4,5,6,7};
    34. int k = 3;
    35. s.rotate(nums, k);
    36. System.out.println(Arrays.toString(nums));
    37. }
    38. }

     直接上面的提交代码就行。

    c++版本的代码提交 

    参考代码:位置(chapter1/c++/rotate-array/lc_accept.cc) 

    1. #include
    2. #include
    3. #include // 包含 reverse 函数的头文件
    4. using namespace std;
    5. class Solution {
    6. public:
    7. void rotate(vector<int>& nums, int k) {
    8. if (nums.empty() || k < 0 || k > 100000) {
    9. return;
    10. }
    11. int len = nums.size();
    12. // 优化 k 的取值
    13. k %= len;
    14. // 反转整个数组
    15. reverse(nums.begin(), nums.end());
    16. // 反转前 k 个元素
    17. reverse(nums.begin(), nums.begin() + k);
    18. // 反转剩余的元素
    19. reverse(nums.begin() + k, nums.end());
    20. }
    21. };
    22. int main() {
    23. Solution s;
    24. vector<int> nums = {1, 2};
    25. int k = 3;
    26. s.rotate(nums, k);
    27. // 输出旋转后的数组
    28. for (int num : nums) {
    29. cout << num << " ";
    30. }
    31. cout << endl;
    32. return 0;
    33. }

    好了,说到这,算法时间复杂度O(n),空间复杂度O(1) 

  • 相关阅读:
    使用 Neuron 接入 Modbus TCP 及 Modbus RTU 协议设备
    MySQL-0-概述-介绍和安装以及MySQL数据模型
    MATLAB算法实战应用案例精讲-【图像处理】机器视觉
    英伟达AI布局的新动向:H200 GPU开启生成式AI的新纪元
    ThreadPoolExecutor详解
    内中断(一)
    文件下载js实现
    Hadoop虚拟化的性能对比和调优经验
    大模型LLM深入浅出、主打通俗易懂
    【Shell脚本12】Shell 输入/输出重定向
  • 原文地址:https://blog.csdn.net/Pxx520Tangtian/article/details/133417813