深圳幻海软件技术有限公司 欢迎您!

剑指offer练习日志01--数组小练习

2023-04-16

目录​一.剑指Offer03. 数组中重复的数字(原地哈希思想)问题描述:问题分析:原地哈希思想排序:题解算法gif: 算法接口:二.二维数组中的查找(😍行列交叉二分法😍)问题描述:方法一:🤔对角元素比较搜索法🤔算法思想:算法gif: 算法接口实现:方法二.😍

目录

一.剑指 Offer 03. 数组中重复的数字(原地哈希思想)

问题描述:

问题分析:

原地哈希思想排序:

题解算法gif: 

算法接口:

二.二维数组中的查找(😍行列交叉二分法😍)

问题描述:

方法一:🤔对角元素比较搜索法🤔

算法思想:

算法gif: 

算法接口实现:

方法二.😍行列交叉二分法😍

基本思想介绍:

算法实现:


一.剑指 Offer 03. 数组中重复的数字(原地哈希思想)

剑指 Offer 03. 数组中重复的数字 - 力扣(Leetcode)https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/

问题描述:

  • 🤪在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。🤪

示例 :

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 

问题分析:

  • 🤪问题中数组长度为n,且其中的元素的值被限制在了 0 ~ n-1的范围内,因此我们将数组的每一个元素m都交换到数组下标为m的位置上(类似于鸽巢思想)(数组下标与元素值建立绝对映射),通过这种方式可以完成时间复杂度为O(N),空间复杂度为O(1)的哈希思想排序,在排序的过程中可以加上简单的重复数字判断语句便可以完成本题的求解.🤪

原地哈希思想排序:

题解算法gif: 

算法接口:

  1. class Solution
  2. {
  3. public:
  4. int findRepeatNumber(vector<int>& nums)
  5. {
  6. int size = nums.size();
  7. int ptr = 0; //用于遍历数组(相当于算法描述中的下标i)
  8. while(ptr<size)
  9. {
  10. int m = nums[ptr];
  11. if(m==ptr) //判断元素的值和其下标是否相同(相同则表明元素已经归位)
  12. {
  13. ++ptr;
  14. }
  15. else
  16. {
  17. while(m != ptr) //内层循环
  18. {
  19. if(m==nums[m]) //判断元素是否重复
  20. {
  21. return m;
  22. }
  23. swap(nums[ptr],nums[m]); //令m元素归位
  24. m=nums[ptr]; //更新m元素,继续完成其余元素的归位
  25. }
  26. }
  27. }
  28. return false;
  29. }
  30. };

  • 😃算法中每个元素只需完成一次归位,因此算法的时间复杂度为O(N)
  • 😃算法的空间复杂度为O(1) 

二.二维数组中的查找(😍行列交叉二分法😍)

剑指 Offer 04. 二维数组中的查找 - 力扣(Leetcode)https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/

问题描述:

  • 😝在一个 n * m 的二维数组中,每一行都按照从左到右 非递减 的顺序排序,每一列都按照从上到下 非递减 的顺序排序。请完成一个高效的函数,输入如前所述的一个二维数组和一个整数target,判断数组中是否含有target

😜示例:

😜现有矩阵 matrix 如下:

  [1,   4,  7, 11, 15]
  [2,   5,  8, 12, 19]
  [3,   6,  9, 16, 22]
  [10, 13, 14, 17, 24]
  [18, 21, 23, 26, 30]
  • 😜给定 target = 5,返回 true
  • 😜给定 target = 20,返回 false

方法一:🤔对角元素比较搜索法🤔

算法思想:

🧐待查找矩阵的行数为Row,列数为Col

🧐矩阵首元素的坐标为(0,0)

  • 🧐选取右上角(或者左下角)元素为key元素
  • 🧐key元素具有如下特征:
  1. 😀key是其所在行最大元素
  2. 😀key是其所在列最小元素
  •  😀每一个查找过程的子步骤中,将target与key进行比较,根据比较结果可以分为三种情况:
  • 😀假设key元素的坐标为(x,y)(第x列,第y行),则我们的查找范围列坐标范围为[0,x],查找范围行坐标范围为[y,Col-1],即我们取以key为右上对角元素的矩阵每个子步骤中待搜索矩阵,于是通过改变(x,y)坐标就可以实现查找范围的缩小:
  • 😀可知,每一次单趟查找我们就可以排除当前待搜索矩阵一行元素一列元素,因此该查找算法的时间复杂度为O(Row + Col).

算法gif: 

算法接口实现:

  1. class Solution
  2. {
  3. public:
  4. bool findNumberIn2DArray(vector<vector<int>>& matrix, int target)
  5. {
  6. int height= matrix.size();
  7. if(height == 0)
  8. {
  9. return false;
  10. }
  11. int lenth = matrix[0].size();
  12. int x = lenth-1; //初始右上角元素横坐标
  13. int y = 0; //初始右上角元素纵坐标
  14. while(x>=0 && y<=height-1) //当x和y其中一个坐标越界时说明target不在矩阵中
  15. {
  16. int key = matrix[y][x];
  17. if(key == target)
  18. {
  19. return true;
  20. }
  21. else if(key>target) //可以排除key所在一列元素
  22. {
  23. --x;
  24. }
  25. else //可以排除key所在一行元素
  26. {
  27. ++y;
  28. }
  29. }
  30. return false; //x或y有一个越界说明target不存在
  31. }
  32. };
  • 注意循环结束的边界条件

方法二.😍行列交叉二分法😍

基本思想介绍:

🧐待查找矩阵的行数为Row,列数为Col

🧐矩阵首元素的坐标为(0,0)

  • 😃如上图所示,查找过程中我们保持LineLeft和RowRight指针不变,待搜索的行范围[RowLeft,RowRight],待搜索的列范围[LineLeft,LineRight]
  • 😃先对第RowLeft行进行二分查找,如上图所示,7不存在于第RowLeft行,则我们需要通过二分查找接口定位比target小最大元素(上图中的4):😃接下来更新LineRight指针(同时令RowLeft加一):
  • 😃这时候我们的查找范围就变成了下图中的矩阵:
  • 😍可见查找范围一下子缩小了一大半😍
  • 😍接下来再对第LineRight列进行二分查找,则可以找到元素7
  • 😍在一般情况下,第LineRight列进行二分查找若没有找到target,我们同样需要通过二分查找接口定位比target小最大元素并且更新RowLeft指针,接着重复上述行列交叉二分查找的过程直到找到target或RowLeft(或LineRight)指针超出边界条件为止
  • 😍上述算法的时间复杂度在大多数情况下可以达到log(Row*Col)(相当于每次单趟查找都可以二分整个矩阵) 

算法实现:

  • 😭算法的边界问题很伤人脑筋😭
  1. class Solution
  2. {
  3. //行二分查找接口
  4. //参数Line标识待查找的行标
  5. bool binaryLineSearch(vector<vector<int>>& arr, int Line, int* returnlimit,
  6. int left, int right, int target)
  7. {
  8. while (right >= left)
  9. {
  10. int mid = left + ((right - left) >> 2);
  11. if (arr[Line][mid] > target) //右边界缩进
  12. {
  13. right = mid - 1;
  14. }
  15. else if (arr[Line][mid] < target) //左边界缩进
  16. {
  17. left = mid + 1;
  18. }
  19. else
  20. {
  21. *returnlimit = mid;
  22. return true;
  23. }
  24. }
  25. *returnlimit = right;//返回行上比target小的最大元素列下标
  26. return false;
  27. }
  28. //列二分查找接口
  29. //参数Row标识待查找的列标
  30. bool binaryRowSearch(vector<vector<int>>& arr, int Row, int* returnlimit,
  31. int left, int right, int target)
  32. {
  33. if (Row < 0)
  34. {
  35. return false;
  36. }
  37. while (right >= left)
  38. {
  39. int mid = left + ((right - left) >> 2);
  40. if (arr[mid][Row] > target) //右边界缩进
  41. {
  42. right = mid - 1;
  43. }
  44. else if (arr[mid][Row] < target) //左边界缩进
  45. {
  46. left = mid + 1;
  47. }
  48. else
  49. {
  50. *returnlimit = mid;
  51. return true;
  52. }
  53. }
  54. *returnlimit = right; //返回列上比target小的最大元素行下标
  55. return false;
  56. }
  57. public:
  58. bool findNumberIn2DArray(vector<vector<int>>& matrix, int target)
  59. {
  60. int height = matrix.size();
  61. if (height == 0)
  62. {
  63. return false;
  64. }
  65. int lenth = matrix[0].size();
  66. //列查找右边界不动
  67. const int Rowright = height - 1;
  68. int RowLeft = 0;
  69. int Row = 0; //用于记录下一次要进行二分查找的行
  70. //行查找左边界不动
  71. const int LineLeft = 0;
  72. int LineRight = lenth - 1;
  73. int Line = 0; //用于记录下一次要进行二分查找的列
  74. //当矩阵只有一行(或一列)元素时,则只需进行一次行(或列)的二分查找
  75. if (LineRight == 0)
  76. {
  77. return binaryRowSearch(matrix, 0, &Line, 0, Rowright, target);
  78. }
  79. else if (Rowright == 0)
  80. {
  81. return binaryLineSearch(matrix, 0, &Row, 0, LineRight, target);
  82. }
  83. //行列交叉二分查找
  84. while (LineRight >= 0 && LineRight <= lenth - 1 && RowLeft >= 0 &&
  85. RowLeft <= height - 1)
  86. {
  87. if (binaryLineSearch(matrix, Line, &Row, LineLeft, LineRight, target))
  88. {
  89. return true; //找到元素返回true
  90. }
  91. LineRight = Row;
  92. ++RowLeft;
  93. if (binaryRowSearch(matrix, Row, &Line, RowLeft, Rowright, target))
  94. {
  95. return true; //找到元素返回true
  96. }
  97. ++Line;
  98. RowLeft = Line;
  99. }
  100. return false;
  101. }
  102. };

 

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览44207 人正在系统学习中