【剑指Offer】力扣刷题日记(1)

 LeetCode是一个程序员刷算法题的软件

《剑指offer》是通行全球的程序员经典面试秘籍


(简单)

1.找出数组中重复的数字

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

例:

输入:

【2,3,1,0,2,5,3】

输出:23

 解:

(1)基础正常解法就是利用两个for循环遍历数组,找到相同的元素就返回

class Solution
{
public:
	int findNumber(vector<int>v)
	{
		for (int i = 0; i < v.size() - 1; i++)
		{
			for (int j = i + 1; j < v.size() - 1; j++)
			{
				if (v[i] == v[j])
				{
					return v[i];
				}
			}
		}
	}
};

但时间复杂度太大,需要更高效

(2)利用原地变换的方法来解决

即若数组下标与其值不对应(a[0]=2),则将以该值为下标的元素与该元素互换(a[2]=3,换完后a[0]=3,a[2]=2),若继续不对应则继续换,直到a[0]=0才往下走,经过交换后各个下标尽可能的会对应其值,此时若出现重复的值去找其对应下标就会发现值一样,即重复数字

class Solution
{
public:
	int findNumber(vector<int>v)
	{
		int i = 0;
		while (i < v.size() - 1)
		{
			if (v[i] != i)
			{
				swap(v[i], v[v[i]]);//交换值
			}
			else if(v[i]==v[v[i]])
			{
				return v[i];//若相等则重复
			}
			else
			{
				i++;
			}
	    }
	}
};
总结:若遇到查找重复元素的问题,可试着让元素与其对应下标或标签移动,此时就比较容易发现重复元素

(中等)

2. 二维数组中的查找

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

例:

现有矩阵如下:

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

查找元素4,返回TRUE

查找元素99,返回FALSE

解:

若直接遍历整个数组肯定达不到高效的要求,所以需要观察矩阵,发现其从左到右增大,从上到下增大,所以就类似一颗二叉树,15为其根节点,第一行左边的都为其左孩子,比他小,15这一列的都为其右孩子,比他大,所以可以利用这种性质,让查找元素与根节点比较,就像查找二叉树的元素一样

即若查找元素比15小则删除所在的列,不再需要考虑这一列(不再考虑右孩子),若查找元素比15大则删除所在行,不再需要考虑这一行(不再考虑左孩子),若相等则找到了

int findDate(vector<vector<int>>v, int date)
{
	//i为行,j为列
	for (int i = 0, j = 4; i<v.size() - 1, j>-1;)
	{
		if (date == v[i][j])
		{
			return TRUE;
		}
		if (date < v[i][j])
		{
			j--;//删除列
		}
		else
		{
			i++;//删除行,即逻辑上不再访问
		}
	}
	return FALSE;
}

总结:发现其规律,大多数题都与数据结构挂钩,可根据特点与特性进行联想

左小右大为二叉树,先进先出为队列,先进后出为栈,一条逻辑链接为链表 


 (简单)

3.请实现一个函数,把字符串s中的每个空格替换成"%100"

例:

输入:s  =  "we  are  happy"

输出:"we%100are%100happy"

很明显,将空格替换后字符串长度变长了,所以需要计算新的字符串长度,而这与空格数量相关,然后要进行替换则必须移动元素,否则空间不够替换

若从前往后替换,则需要移动大量的元素(一旦字符串长),所以很明显我们需要从后往前替换(例题字符串比较短容易忽略这个问题),我们赋予字符串新的长度后,后边是有足够刚好的空间来给我们进行元素的移动的,从后往前移还可以让元素不被覆盖 

string replacement(string s)
{
	int x = 0;//标记空格数量
	for (int i = 0; i < s.size() - 1; i++)
	{
		if (s[i] == ' ')
		{
			x++;
		}
	}
	//%100比空格多占3个位,所以新的位数为空格数*3
	int len = s.size();
	s.resize(len + 3 * x);

	//从后往前替换,利用两个指针的方法(不一定得是真的指针)
	for (int i = len - 1, int j = s.size() - 1; i < j; i--, j--)
	{
		if (s[i] != ' ')
		{
			s[j] = s[i];//若不是空格则将该元素移动到后方,腾出位置
		}
		else
		{
			s[j - 3] = '%';
			s[j - 2] = '1';
			s[j - 1] = '0';
			s[j] = '0';
			j -= 3;//若遇到空格则替换为%100,记得类指针j需要后移3位
		}
	}
    return s;
}

 (简单)

4.从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)

例:

输入:head=[1 , 2 , 3 ]

输出:[2 , 3 , 1 ]

可利用栈的先进后出的特点进行存储再输出

struct ListNode
{
	int val;
	ListNode* next;
};
class Solution
{
public:
	vector<int>reversePrint(ListNode* head)
	{
		stack<int>s;
		ListNode* p = head;
		//遍历单链表
		while (p != NULL)
		{
			s.push(p->val);
			p = p->next;
		}
		//用数组返回结果
		vector<int>v;
		while (s.empty() != NULL)
		{
			v.push_back(s.top());//返回栈顶元素
			s.pop();//出栈
		}
		return v;
	}
};

(中等)

5.重建二叉树

输入某二叉树的先序遍历和中序遍历的结果,请构建该二叉树并返回其根节点

假设输入的先序遍历和中序遍历的结果中都不含重复的数字

例:

1.

输入:preorder先序 = [ 3 , 9 , 20 , 15 , 7 ]

inorder中序 = [9 , 3 , 15 , 20 , 7 ]

输出:[ 3 , 9 , 20 , null , null , 15 , 7 ]

2.

输入:preorder = [ -1 ] , inorder = [ -1 ]

输出:[ -1 ]

class TreeNode
{
public:
	TreeNode* left;
	TreeNode* right;
	int date;
};
class TreeNodeRoot
{
public:
	TreeNode* root;
};
class Solution
{
private:
	map<int, int>index;//定义一个哈希表来定位中序遍历中的根节点,以求快速找到
public:
	//构造哈希表
	//哈希表的键值用中序数组的值来表示
	//哈希表的值用中序数组的值的下标来表示
	//如此便可以根据键值(根节点)来快速查找下标
	TreeNode* Index(vector<int>& preorder, vector<int>& inorder)
	{
		int n = preorder.size();

		for (int i = 0; i < n; i++)
		{
			//将下标与值一一对应
			index[inorder[i]] = i;
		}
	}
	//参数分别为先序遍历数组,中序遍历数组,先序遍历左节点的下标,先序遍历右节点的下标,中序遍历左右节点的下标
	TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right)
	{
		if (preorder_left > preorder_right)
		{
			return  nullptr;//若先序左节点的下标大于右节点的下标,则表明其左孩子个数为0,返回空树
		}

		//在先序遍历中,第一个节点就是根节点
		int preorder_root = preorder_left;//将下标赋给根节点

		//在中序遍历中,根据哈希表查找根节点位置,定位根节点
		int inorder_root = index[preorder[preorder_root]];

		//把根节点建立出来
		TreeNode* root = new TreeNode(preorder[preorder_root]);

		//得出其左孩子的数量
		int size_left = inorder_root - inorder_left;

		//利用递归构造左孩子的树
		//参数中,先序左节点位置加一寻找下一个根节点,右节点为左节点的位置初值加上左节点个数,中序右节点为根节点减一
		root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left, inorder_left, inorder_root - 1);

		//递归构造右孩子的树
		//参数中,先序左节点为左节点初值位置加上左节点个数加1,中序左节点为根节点位置加1
		root->right = myBuildTree(preorder, inorder, preorder_left + size_left + 1, preorder_right, inorder_root + 1, inorder_right);

		return root;
	}
};

(简单)

6.用两个栈实现一个队列

队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead操作返回-1)

例:

输入:

1 , 2 , 3 , 4 , 5

输出:

5 , 4 , 3 , 2 , 1

栈的特点是先进后出,队列的特点是先进先出

用两个栈来实现队列,即由一个栈来输入队列中的元素,最先输入的元素会在栈底,再建一个栈来输出元素,把输入栈的元素输出到第二个栈,那么最后输入的元素会在第二个栈的栈底,当第二个栈进行输出时就相当于队列的输出,后来的最后输出

class Solution
{
public:
	//定义两个栈,输入和输出元素
	stack<int>inStack, outStack;

	int outDate(stack<int>inStack, stack<int>outStack)
	{
		for (int i = 1; i < 6; i++)
		{
			//为输入栈赋值
			inStack.push(i);
		}

		//若输入栈为空则停止
		while (inStack.empty())
		{
			//将输入栈的元素从栈顶开始输入到输出栈
			outStack.push(inStack.top());
			//删除输入栈的元素
			inStack.pop();
		}

		for (int i = 1; i < 6; i++)
		{
			int date = outStack.top();
			outStack.pop();
			return date;
		}
	}
};

(简单)

7.斐波那契数列

写一个函数,输入n,求斐波那契数列的第n项。斐波那契数列由0和1开始,之后的斐波那契数就是由之前的两数相加而得出。

例:

斐波那契数列:

0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21

 斐波那契数列就是由两个数相加得到第三个数,可通过迭代的方法求出

int Fibonacci(int n)
{
	//x为判断要查找的斐波那契数是单数还是双数
	//y为计量查找的轮数
	//i和j为斐波那契初始数
	int x = 0, y = 0, i = 0, j = 1;

	if (n % 2 != 0)
	{
		 x = n + 1;
	}
	else
	{
		 x = n;
	}
	//这里为主要实现,采用迭代的方法,为i和j不断重新赋值
	for ( i = 0, j = 1; y < (x / 2) - 1; y++)
	{
		i = i + j;
		j = i + j;
	}

	if (x % 2 == 0)
	{
		return j;
	}
	else
	{
		return i;
	}
}

(简单)

8. 青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上n级台阶总共有多少种跳法

例:

输入:n=1

输出:1

·

输入:n=0

输出:1

·

输入:n=7

输出:21

青蛙跳上第n级台阶时,只有两种跳法,即跳1级或2级

当为1级跳法,则表示跳到n级台阶有F(n-1)种跳法

当为2级跳法,则表示跳到n级台阶有F(n-2)种跳法

由此跳到n级台阶就有F(n)=F(n-1)+F(n-2)种跳法

这就是一个斐波那契数列(前一个加上后一个数得出第三个数),与斐波那契数列的不同点在于斐波那契数列是从0,1开始的,这个是由1,1开始的(条件0级台阶为1种跳法)


(简单)

9.旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在重复元素元素值的数组numbers,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。

例如:

数组【3,4,5,1,2】为【1,2,3,4,5】的一次旋转,该数组的最小值为1

注意:

数组【a[0],a[1],······a[n-1]】旋转一次的结果为数组【a[n-1],a[0],a[1]······,a[n-2]】

int main()
{
	vector<int>v;
	Spin(v, 5, 2);
	system("pause");
	return 0;
}

//n为要插入的元素个数,q为要进行的旋转次数
void Spin(vector<int>v, int n, int q)
{
	//利用动态数组对元素进行升序插入
	for (int i = 0; i < n; i++)
	{
		v.push_back(i);
	}

	//x为对最后一个元素的寄存,y为对旋转次数的计量
	int x, y = 0;
	while (y < q)
	{
		x = v[n-1];
		//从倒数第二个元素开始,从后逐个往前移
		for (int a = n - 2; a > -1; a--)
		{
			v[a + 1] = v[a];
		}
		//再把最后一个元素插入到第一个进行旋转
		v[0] = x;
		y++;
	}

	//可以发现,旋转的次数就是v[0]即第一个元素的后移次数
	//由于是升序,所以第一个元素肯定最小,找到第一个元素的位置就是最小元素
	cout << v[y] << endl;
}


(中等)

10.矩阵中的路径

给定一个m*n二维字符网格board和一个字符串单词word。如果word存在于网格中,返回true,否则返回false

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一单元格内的字母不允许被重复使用 

例:

ABCE
SFCS
ADEE

输入:

board = 【A , B , C , E 】,【S , F , C , S 】,【A , D , E , E】,word = ABCCED

输出:true

·

输入:

board = 【a , b 】,【c,d】,word=abcd

输出:false

即不能直接连续几行或几列输入word,只能从网格中连续横向,列向和横向加上列向输入 

int Matrix(vector<vector<char>>v, int x, int y,string q)
{
	cout << "输入网格的值" << endl;
	char date;
	//为网格赋值
	for (int a = 0; a < x; a++)
	{
		for (int b = 0; b < y; b++)                        
		{
			cout << "--" << endl;
			cin >> date;
			v[a][b] = date;
		}
	}

	//设置两个量来记录坐标的变化
	int k1, z1 = 0;

	//对字符串的每个元素进行查找对比
	for (int i = 0; i < q.size(); i++)
	{
		//对每个值进行网络内的遍历查找,返回其坐标
		int k,z = Search(v, q[i],x,y);

		//若找不到值则直接返回FALSE
		if (k == 0 && z == 0)
		{
			return FALSE;
		}

		//后面的值的坐标与前面的值的坐标对比
		//只有与横坐标相邻,纵坐标相邻的元素才是合格的,第一个元素不需要对比只需要查找
		if (i != 0 && (k1 - k != 1 || k1 - k != -1) && (z1 - z != 1 || z1 - z != -1))
		{
			return FALSE;
		}

		 k1 = k;
		 z1 = z;
		
	}
	
	return TRUE;
}

int Search(vector<vector<char>>v1, char p,int x,int y)
{
	for (int i = 0; i < x; i++)
	{
		for (int j = 0; j < y; j++)
		{
			if (v1[i][j] == p)
			{
				return i, j;
			}
		}
	}
	return 0, 0;
}

这是一种暴力的解法,从字符串的第一个元素开始,每个元素都需要经历查找,再对比上个元素的位置,只有相邻才继续遍历,所以时间复杂度会较高

若想要较低的时间复杂度,可以考虑用递归

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值