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

剑指offer练习日志01--数组小练习_第1张图片

目录

一.剑指 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)的哈希思想排序,在排序的过程中可以加上简单的重复数字判断语句便可以完成本题的求解.剑指offer练习日志01--数组小练习_第2张图片

原地哈希思想排序:

剑指offer练习日志01--数组小练习_第3张图片

题解算法gif: 

剑指offer练习日志01--数组小练习_第4张图片

算法接口:

class Solution 
{
public:
    int findRepeatNumber(vector& nums) 
    {
        int size = nums.size();
        int ptr = 0;           //用于遍历数组(相当于算法描述中的下标i)
        while(ptr

剑指offer练习日志01--数组小练习_第5张图片

  • 算法中每个元素只需完成一次归位,因此算法的时间复杂度为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是其所在列最小元素剑指offer练习日志01--数组小练习_第6张图片
  •  每一个查找过程的子步骤中,将target与key进行比较,根据比较结果可以分为三种情况:剑指offer练习日志01--数组小练习_第7张图片
  • 假设key元素的坐标为(x,y)(第x列,第y行),则我们的查找范围列坐标范围为[0,x],查找范围行坐标范围为[y,Col-1],即我们取以key为右上对角元素的矩阵每个子步骤中待搜索矩阵,于是通过改变(x,y)坐标就可以实现查找范围的缩小:剑指offer练习日志01--数组小练习_第8张图片
  • 可知,每一次单趟查找我们就可以排除当前待搜索矩阵一行元素一列元素,因此该查找算法的时间复杂度为O(Row + Col).

算法gif: 

剑指offer练习日志01--数组小练习_第9张图片

算法接口实现:

class Solution 
{
public:
    bool findNumberIn2DArray(vector>& matrix, int target) 
    {
        int height= matrix.size();
        if(height == 0)
        {
            return false;
        }
        int lenth = matrix[0].size();
        int x = lenth-1;  //初始右上角元素横坐标
        int y = 0;        //初始右上角元素纵坐标
        while(x>=0 && y<=height-1)  //当x和y其中一个坐标越界时说明target不在矩阵中
        {
            int key = matrix[y][x];
            if(key == target)
            {
                return true;
            }
            else if(key>target)  //可以排除key所在一列元素
            {
                --x;
            }
            else                 //可以排除key所在一行元素
            {
                ++y;
            }
        }
        return false;            //x或y有一个越界说明target不存在
    }
};
  • 注意循环结束的边界条件

剑指offer练习日志01--数组小练习_第10张图片

方法二.行列交叉二分法

基本思想介绍:

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

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

剑指offer练习日志01--数组小练习_第11张图片

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

算法实现:

  • 算法的边界问题很伤人脑筋
class Solution
{
	//行二分查找接口
    //参数Line标识待查找的行标
	bool binaryLineSearch(vector>& arr, int Line, int* returnlimit,
		                  int left, int right, int target)
	{

		while (right >= left)
		{
			int mid = left + ((right - left) >> 2);
			if (arr[Line][mid] > target)		    //右边界缩进
			{
				right = mid - 1;
			}
			else if (arr[Line][mid] < target)       //左边界缩进
			{
				left = mid + 1;
			}
			else
			{
				*returnlimit = mid;
				return true;
			}
		}
		*returnlimit = right;						//返回行上比target小的最大元素列下标
		return false;
	}

	//列二分查找接口
    //参数Row标识待查找的列标
	bool binaryRowSearch(vector>& arr, int Row, int* returnlimit,
		                 int left, int right, int target)
	{
		if (Row < 0)
		{
			return false;
		}
		while (right >= left)
		{
			int mid = left + ((right - left) >> 2);
			if (arr[mid][Row] > target)				   //右边界缩进
			{
				right = mid - 1;
			}
			else if (arr[mid][Row] < target)           //左边界缩进
			{
				left = mid + 1;
			}
			else
			{
				*returnlimit = mid;
				return true;
			}
		}
		*returnlimit = right;                         //返回列上比target小的最大元素行下标
		return false;
	}


public:
	bool findNumberIn2DArray(vector>& matrix, int target)
	{
		int height = matrix.size();
		if (height == 0)
		{
			return false;
		}
		int lenth = matrix[0].size();



		//列查找右边界不动
		const int  Rowright = height - 1;
		int RowLeft = 0;
		int Row = 0;     //用于记录下一次要进行二分查找的行


		//行查找左边界不动
		const int LineLeft = 0;
		int  LineRight = lenth - 1;
		int Line = 0;    //用于记录下一次要进行二分查找的列



		//当矩阵只有一行(或一列)元素时,则只需进行一次行(或列)的二分查找
		if (LineRight == 0)
		{
			return binaryRowSearch(matrix, 0, &Line, 0, Rowright, target);
		}
		else if (Rowright == 0)
		{
			return binaryLineSearch(matrix, 0, &Row, 0, LineRight, target);
		}



		//行列交叉二分查找
		while (LineRight >= 0 && LineRight <= lenth - 1 && RowLeft >= 0 && 
               RowLeft <= height - 1)
		{
			if (binaryLineSearch(matrix, Line, &Row, LineLeft, LineRight, target))
			{
				return true;      //找到元素返回true
			}
			LineRight = Row;
			++RowLeft;
			if (binaryRowSearch(matrix, Row, &Line, RowLeft, Rowright, target))
			{
				return true;     //找到元素返回true
			}
			++Line;
			RowLeft = Line;
		}
		return false;
	}
};

剑指offer练习日志01--数组小练习_第16张图片

 剑指offer练习日志01--数组小练习_第17张图片

你可能感兴趣的:(剑指offer练习日志,数据结构,算法)