笔记本题目 复习(一)

数组中出现次数超过一半的数字 哈希表的经典应用

这里要求的是出现次数超过一半 ,如何指定出现的次数为具体的值,那么只需将代码中 p.second==一个具体的值 即可


class Solution
{
public:
	int majorityElement(vector<int>& nums)
	{
		int len = nums.size();
		unordered_map<int, int>table;
		for (int num : nums)table[num]++;
		int res = 0;
		for (auto p : table)
		{
			if (p.second >(len / 2)) return res = p.first;
		}
		return res;
	}
};

剑指offer 11 旋转数组的最小数字

题目要求:
给你一个可能存在重复元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。
例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为1

在这里插入图片描述

C++实现

//双指针+二分查找的方法
class Solution {
public:
    int findMin(vector<int>& nums) {
        int l = 0;
        int r = nums.size() - 1;
        while (l < r) {
            int mid = l + (r - l) / 2;
            if (nums[mid] > nums[r]) //例如数组[3 4 5 1 2 ] 更新左边界 缩小之后的搜索范围是[1 2]
            {          
                l = mid + 1;
            } 
            else if(nums[mid]<nums[r]) //例如数组[5 1 2 3 4] 更新右边界 缩小之后的搜索范围是[5 1 2]
            {                               
                r = mid;
            }
            else r--;
        }
        return nums[l];
    }
};

leetcode 33 搜索旋转排序数组 I

给定一个旋转之后的数组nums和一个整数target,如果nums中不存在这个目标值target,返回它的下标,否则返回-1;
例如:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

算法思路:
第一个方法:直接遍历搜索

class Solution2
{
public:
	int search(vector<int>& nums, int target)
	{
		int n = 0;
		for (auto num : nums)
		{
			if (num != target)n++;
			else return n;
		}
		return -1;
	}
};


//=================================哈希表解法========================================================
class Solution
{
public:
	int search(vector<int>& nums, int target)
	{
		unordered_map<int, int>mp;
		for (int i = 0; i < nums.size(); i++)
		{
			mp[nums[i]]= i;//哈希表记录元素的下标
		}
		int res = 0;
		for (auto p : mp)
		{
			if (p.first == target)
			{
				return res = p.second;
			}
		}
		return res;
	}
};

第二个方法: 用二分查找的思想:
整体分两种大的情况
情况一:target<=nums[mid]
在这个前提下,判断target是在数组的左半部分nums[l]<target<nums[mid]还是右半部分nums[mid]<target<num[r]
情况二:target>nums[mid]
进行同样的讨论,判断target是在数组的左半部分nums[l]<target<nums[mid]还是右半部分nums[mid]<target<num[r]

代码实现:



class Solution
{
public:
	int search(vector<int>& nums, int target)
	{
		int l = 0, r = nums.size() - 1;
		while (l <= r)
		{
			int mid = l + (r - l) / 2;
			if (target == nums[mid])return mid;
			if (target <= nums[mid])   
			{
				if (target >= nums[l] && target < nums[mid]) r = mid - 1; //目标值位于左半部分
				else l = mid + 1;  //目标值位于右半部分
			}
			else
			{
				if (target > nums[mid] && target <= nums[r]) l = mid + 1;
				else r = mid - 1;
			}
		}
		return -1;
	}
};

int main()
{
	Solution so;
	vector<int>nums = { 4, 5, 6, 7, 0, 1, 2 };
	int res;
	res = so.search(nums, 2);
	cout << "目标值的下标是:" << res << endl;
	
	system("pause");
	return 0;
}


判断一个字符串s1是不是包含另一个字符串s2

给你两个字符串s1 s2 写一个函数来判断s2是否包含s1的排列 如果是 返回true 如果不是 返回false
例如:
输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").
在这里插入图片描述

//利用哈希表加双指针的方法
//设置一个双指针 右指针先移动一个单位 当遍历到哈希表中没有出现过的元素时 左指针也开始移动一个单位 和右指针重合;
//当遇到哈希表中出现过的元素时,右指针移动  左指针不移动 最后左右指针的下标之差符合我们要找的字符串的长度 说明我们在s2中找到了s1的一个排列;
class Solution
{
public:
	bool checkInclusion(string s1, string s2)
	{
		unordered_map<char, int>map;//集合中的值默认初始化为0 哈希表只存储s1中出现的元素
		for (auto& c : s1)map[c]++;
		int l = 0, r = 0;
		while (r < s2.size())
		{
			char c = s2[r++];
			map[c]--;//当遇到在s2中出现 在s1中没出现过的元素时,值会减为负数
			while (l < r && map[c] < 0)
			{
				map[s2[l++]]++;//将在s2中出现过但是没在s1中出现过的元素的出现次数加一, 跳出当前的while循环 进入上一个循环,从而进行下一步判断
			}
			if (r - l == s1.size())return true;
		}
		return false;
	}
};

leetcode 151 颠倒字符串中的单词 (反转单词顺序)

istringstream类 需要包含<sstrream>这个头文件,执行C++风格的串流的输入操作;
它的作用是从string对象str中读取字符
例如:
输入:s = "the sky is blue"
输出:"blue is sky the"


//反转单词顺序
class Solution
{
public:
	string reverseWords(string s)
	{
		stack<string>stk;//创建栈
		string res, str;//str用于读取字符;str用于拼接输出字符
		istringstream ss(s);//创建输入流类的对象 /ss可以从s中读取字符
		while (ss >> str)//ss从s中提取到的字符是str
		{
			stk.push(str);
			stk.push(" ");
		}
		if (!stk.empty())//这次循环的作用是把栈顶的空字符串删除
		{
			stk.pop();
		}
		while (!stk.empty())//将单词依次拼接
		{
			res += stk.top();
			stk.pop();
		}
		return res;
	}
};


leetcode 165 比较版本号 M

题目要求:
给定两个版本号,比较其大小;
给你两个版本号 version1 和 version2 ,请你比较它们。

版本号由一个或多个修订号组成,各修订号由一个 ‘.’ 连接。每个修订号由 多位数字 组成,可能包含 前导零 。每个版本号至少包含一个字符。修订号从左到右编号,下标从 0 开始,最左边的修订号下标为 0 ,下一个修订号下标为 1 ,以此类推。例如,2.5.33 和 0.1 都是有效的版本号。

比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较忽略任何前导零后的整数值 。也就是说,修订号 1 和修订号 001 相等 。如果版本号没有指定某个下标处的修订号,则该修订号视为 0 。例如,版本 1.0 小于版本 1.1 ,因为它们下标为 0 的修订号相同,而下标为 1 的修订号分别为 0 和 1 ,0 < 1 。

返回规则如下:

如果 version1 > version2 返回 1,
如果 version1 < version2 返回 -1,
除此之外返回 0。

C++ 实现 提交时显示错误,看了一下,思路应该没啥问题,出现了边界溢出问题;没找到很好的解决办法;


//比较版本号 双指针解法
//算法思路:
//使用两个指针i和j分别指向两个字符串的开头,然后向后遍历,遇到小数点就停下来,并将每个小数点分隔开的修订号解析成数字进行比较;
//给定的是字符串 小数点会把这个字符串分割成好几部分,我们先把这几个部分解析成数字
//我们将每一部分都看成是一个单独的数字,单独比较这几个数字的大小,从而确定整个修订版本号的大小

class Solution
{
public:
	double compareVersion(string version1, string version2)
	{
		int i = 0,j = 0;
		while (i < version1.size() || j < version2.size())
		{
			int num1 = 0, num2 = 0;
			while (i < version1.size() && version1[i] !='.')num1 = num1 * 10 + version1[i++] - '0';//将一段连续的字符串转换成数字
			while (j < version2.size() && version2[j] !='.')num2 = num2 * 10 + version2[j++] - '0';
			if (num1 > num2)return 1;
			else if (num1 < num2)return -1;
			i++;
			j++;
		}
		return 0;//如果遍历完两个字符串都没有返回相应的结果,说明两个字符串相等,返回0;
	}
};


提交成功的解法

//用C++中的istringstream 需要包含<sstream>这个头文件
class Solution
{
public:
	int compareVersion(string version1,string version2)
	{
		char c;//用于读取 .
		int num1, num2;//用于读取数字
		istringstream str1(version1);//执行C++风格的串流的输入操作,作用是从string对象str中读取字符;
		istringstream str2(version2);

		while (bool(str1 >> num1) + bool(str2 >> num2))//每次读取的数字是以.分割开的数字
		{
			if (num1 > num2)return 1;
			if (num1 < num2)return -1;
			//以.为分界  先读取数字 再读取字母 .  比较每一部分数字的大小;
			num1 = 0;//将第一部分的数字置零 进行下一部分数字的比较
			num2 = 0;
		  str1 >> c;//读取. C++中标准输入输出中所使用的">>“和”<<"是重载运算符作用,意义是流提取运算付和流插入运算符;
			str2 >> c;//读取.
		}
		return 0;
	}
};


反转字符串

输入:s=["h","e","l","l","o"]
输出: ["o","l","l","e","h"]

C++ 实现:

//反转字符串
class Solution
{
public:
	void reverseString(vector<char>& s)
	{
		for (int i = 0,j = s.size() - 1; i < s.size() / 2;i++, j--)
		{
			swap(s[i], s[j]);
		}
	}
};

对每隔2k元素的前k个元素进行反转

在这里插入图片描述


//对每隔2k个元素的前k个元素进行反转
class Solution
{
public:
	string reverseStr(string s, int k)
	{
		for (int i = 0; i < s.size(); i+= (2 * k))//以2k个单位依次递增
		{
			if (i + k < s.size())
			{
				reverse(s.begin() + i, s.begin() + i + k);//反转每组的k个元素
			}
			reverse(s.begin() + i, s.begin() + s.size());//当给定字符串长度大于k时,直接反转整个字符串即可
		}
		return s;
	}
};

移除数组中的指定元素

class Solution2
{
public:
	int removeElement(vector<int>& nums, int val)
	{
		int index = 0;//用于统计新数组的长度
		for (auto num : nums)
		{
			if (num != val)nums[index++] = num;
		}
		return index;
	}
};

int main()
{
	Solution2 so;
	int res = 0;
	vector<int>nums = { 3,2,2,3 };
	res=so.removeElement(nums, 2);
	cout << "移除元素之后的数组的新长度是:" << res << endl;
	//return res;
	system("pause");
	return 0;
}

反转链表

在这里插入图片描述
C++实现


//迭代解法
class Solution
{
public:
	ListNode* reverseList(ListNode* head)
	{
		ListNode* pre = nullptr;
		ListNode* cur = head;
		while (cur)
		{
			ListNode* temp = cur->next;
			cur->next = pre;//实现空节点和头节点的反转
			pre = cur;//继续递推实现头节点和第二个节点的反转
			cur = temp;
		}
		return pre;//返回原链表的尾节点即新链表的头节点
	}
};


反转链表 II

题目要求:

给定单链表的头指针head和两个整数left和right,其中left<=right.请你反转从位置left到位置right的链表节点,返回反转后的链表;

在这里插入图片描述在这里插入图片描述

在这里插入图片描述

算法思路:

整体思想是,在待反转区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置

在这里插入图片描述
具体实现:
使用三个指针变量pre,cur,next来记录反转的过程中需要的变量,它们的意义如下:
cur:指向待反转区域的第一个节点left;
next:永远指向cur指针的下一个节点,循环过程中,cur变化后next会变化
pre:永远指向待反转区域的第一个节点left的前一个节点,在循环过程中不变;

在这个思路下:
我们进行实现上述五步:
第一步:我们使用 ①、②、③ 标注穿针引线的步骤。

操作步骤

先将 curr 的下一个节点记录为 next;
执行操作 ①:把 curr 的下一个节点指向 next 的下一个节点;
执行操作 ②:把 next 的下一个节点指向 pre 的下一个节点;
执行操作 ③:把 pre 的下一个节点指向 next。

第 1 步完成以后「拉直」的效果如下:
在这里插入图片描述
第 2 步,同理。同样需要注意 「穿针引线」操作的先后顺序。
在这里插入图片描述
第 2 步完成以后「拉直」的效果如下:
在这里插入图片描述
第 3 步,同理。
在这里插入图片描述
第 3 步完成以后「拉直」的效果如下:
在这里插入图片描述

C++实现


//反转区间[left,right]之间的字符串
class Solution
{
public:
	ListNode* reverseBetween(ListNode* head, int left, int right)
	{
		ListNode* dummyHead = new ListNode(0);
		dummyHead->next = head;
		ListNode* pre = dummyHead;
		for (int i = 0; i < left - 1; i++)
		{
			pre = pre->next;//首先将pre指针移动到待反转区域的前一个节点
		}  //循环结束后 pre指针指向待反转区域的前一个节点  cur指针指向待反转区域的第一个节点
		ListNode* cur = pre->next;//cur指针指向待反转区域的第一个节点
		for (int i = 0; i < right -left; i++)
		{
			ListNode* temp = cur->next;//实现节点的逐步插入 从而实现反转
			cur->next = temp->next;
			temp->next = pre->next;
			pre->next = temp;
		}
		return dummyHead->next;
	}
};

leetcode 328 奇偶链表

题目要求:

给定单链表的头节点head,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表;
默认,第一个节点的索引为奇数,第二个节点的索引为偶数;
例如:
输入:head=[1,2,3,4,5]
输出:[1,3,5,2,4]

算法思路:
在这里插入图片描述

C++实现


struct ListNode
{
	int val;
	ListNode* next;
	ListNode(int x):val(x),next(nullptr){}
};
class Solution
{
public:
	ListNode* oddEventList(ListNode* head)
	{
		if (!head || !head->next)return head;//至少前两个节点都存在才能进行接下来的操作;
		ListNode* pre = head, * cur = head->next;//pre指针指向奇数链表  cur指针指向偶数链表;
		while (cur && cur->next)
		{
			ListNode* temp = pre->next;//用一个节点暂存pre->next 也就是每次衔接点的位置,因为接下来的操作会让链表断开;
			pre->next = cur->next;//将奇数节点连接成链
			cur->next = cur->next->next;//将偶数节点连接成链
			pre->next->next = temp;//奇偶衔接的地方
			//每四个节点为一个循环 依次类推;继续向下调整顺序 对于已经调整好的顺序 不需要重复操作;
			pre = pre->next;
			cur = cur->next;
		}
		return head;
	}
};

二叉搜索树与双向链表

题目要求:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表;
在这里插入图片描述

前驱节点不为空时,将前驱节点pre的右指针指向当前根节点root,即pre->right=root;
在这里插入图片描述
修改双向指针
在这里插入图片描述
pre指针后移 继续向下遍历
在这里插入图片描述

最后pre和head互指
在这里插入图片描述

C++实现:


struct Node
{
	int val;
	Node* left, * right;
	Node(int x) :val(x),left(nullptr),right(nullptr){}

};

class Solution
{
public:
	Node* pre = nullptr,*head = nullptr;//pre指针用于保存中序遍历中的前一个节点 head指针用于记录排序链表的头节点;
	Node* treeToDoublyList(Node* root)
	{
		if (!root)return root;
		dfs(root);
		head->left = pre;//实现循环链表
		pre->right = head;
		return head;
	}
	void dfs(Node* root)
	{
		//中序遍历的遍历顺序就是循环链表的建立顺序
		if (!root)return;//递归终止条件
		dfs(root->left);//递归左子树

		if (pre)pre->right = root;//修改从pre到root的单向指针;
		else head = root;//保存链表头节点
		root->left = pre;//修改从root到pre的单向指针;此时就构造好了pre和root之间的双向指针;
		pre = root;//更新 继续向下遍历 pre不断向后移动 以指向每一个遍历到的节点
		
		dfs(root->right);
	}
};


leetcode 143 重排链表 & 剑指offer II 026 重排链表

题目要求:
输入:head=[1,2,3,4]
输出:[1,4,2,3]

输入:[1,2,3,4,5]
输出:[1,5,2,4,3]

在这里插入图片描述

小注
emplace_back 相对于push_back 在容器尾部添加元素时,这个元素会在原地构造,不需要触发拷贝构造和转移构造

C++ 实现


//重排链表
class Solution
{
public:
	void reorderList(ListNode* head)
	{
		if (head == nullptr)return;
		vector<ListNode*>vec;//创建一个容器用于存放所有的节点
		ListNode* node = head;//Node是一个起到遍历作用的指针
		while (node)
		{
			vec.emplace_back(node);//第一步 将链表中的所有节点都加入到容器中
			node = node->next;
		}//先将所有的节点存放到容器中 再对容器中的节点进行重排的操作
		int i = 0, j = vec.size() - 1;//双指针
		while (i < j)
		{
			vec[i]->next = vec[j];//将后面的节点依次插入到前面的节点中间
			i++;
			if (i == j)break;
			vec[j]->next = vec[i];//连接起来 
			j--;
		}
		vec[i]->next = nullptr;//断开原链表的连接
	}
};


leetcode 130 逆波兰表达式求值

给定一个表达式 按后缀表达式的方法求解

例如:
输入:tokens=[4,13,5,/,+]
输出:6 中缀表达式算式为:(4+(13/5))=6

核心思路:
计算逆波兰表达式求值时,使用一个栈存储操作数,从左到右遍历逆波兰表达式:
遇到数字,将数字入栈
遇到运算符,则将栈顶最近的两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将计算得到的结果保存到栈里即可;
整个逆波兰表达式遍历完成后,栈内只有一个元素,该元素即为逆波兰表达式的值;

C++实现:

//逆波兰表达式求值(后缀表达式)用栈的方法实现
//后缀表达式的求解思路:
//最近的两个数字与最近的一个运算符进行运算;以此类推 

class Solution
{
public:
	int evalRPN(vector<string>& tokens)
	{
		stack<int>stk;//因为要进行具体的加减乘除运算  所以要转换成整型数字
		int n = tokens.size();
		for (int i = 0; i < n; i++)//遍历传入的数组
		{
			string& token = tokens[i];//定义一个引用  token是数组tokens第i个元素的引用
			if (isNumber(token))//该元素是数字  直接入栈
			{
				stk.push(atoi(token.c_str()));//atoi是将字符串转为整数 c_str返回当前字符串的首地址 string 是C++ STL定义的类型,atoi是 C 语言的库函数,所以要先转换成 char* 类型才可以用 atoi。
			}
			else//该元素是操作符 将距离栈顶最近的两个元素出栈参于运算
			{
				int num2 = stk.top();//num1和num2分别出栈以后参与运算的两个元素 num2是右操作数
				stk.pop();
				int num1 = stk.top();//num1是左操作数
				stk.pop();
	      switch (token[0])//token[0]//======表示遍历过程中遇到的距离数字最近的那个运算符=====不容易理解的地方=========
				{
				case'+':
					stk.push(num1 + num2);//将运算结果存放栈中
					break;
				case'-':
					stk.push(num1 - num2);
					break;
				case'*':
					stk.push(num1 * num2);
					break;
				case'/':
					stk.push(num1 / num2);
					break;
				}
			}
		}
		return stk.top();//将最后计算的结果出栈 结束遍历 
	}
	
		bool isNumber(string& token)
	{
		if (token == "+")return false;
		else if (token == "-")return false;
		else if (token == "*")return false;
		else if (token == "/")return false;
		else return true;
	}
};


leetcode 242有效的字母异位词E && leetcode 49 字母异位词分组M

有效的字母异位词
判定给定的两个单词是不是字母异位词

使用数组做哈希表,首先用哈希表记录一个单词中的字母出现的次数,然后遍历另一个单词,每遇到一个字母,就在哈希表中递减其出现次数,最后当哈希表中出现次数为0时,说明两个单词中的字母是一模一样的;说明是字母异位词;

C++实现:


//有效的字母异位词
class Solution {
public:
	bool isAnagram(string s, string t)
	{
		int  record[26] = { 0 };//用数组做哈希来用 最后有26个不一样的字母 初始最大化 
		for (int i = 0; i < s.size(); i++)
		{
			record[s[i] - 'a']++;//将大写字母转换为小写字母   递加字母的出现次数
		}
		for (int i = 0; i < t.size(); i++)
		{
			record[t[i] - 'a']--;  //递减字母的出现次数
		}
		for (int i = 0; i < 26; i++)
		{
			if (record[i] != 0)return false;
		}
		return true;
	}
};

字母异位词分组
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

小注:
C++中正向迭代器的定义方式:vector<int>::iterator it
it 就是一个迭代器;

//第二种遍历方式:
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << endl;//对it指针解引用 返回容器中每一个位置的元素
	}
//字母异位词重组
//哈希表+二维数组
class Solution
{
public:
	vector<vector<string>>groupAnagrams(vector<string>& strs)
	{
		unordered_map<string, vector<string>>mp;//key是某个单词,value是由该单词的字母组成的所有字母异位词的集合;
		for (string& str : strs)
		{
			string key = str;
			sort(key.begin(), key.end());
			mp[key].emplace_back(str);//str是key的所有字母异位词的集合
		}
		vector<vector<string>>ans;//创建一个二维数组用于存放这些字母异位词
		for (auto it = mp.begin(); it != mp.end(); it++)//范围for循环;
		{
			ans.emplace_back(it->second);
		}
		return ans;
	}
};

二分查找的写法

//二分查找的几种形式
//普通的二分查找
class Solution
{
public:
	int search(vector<int>& nums, int target)
	{
		int left = 0, right = nums.size() - 1;
		while (left <= right)
		{
			int mid = left + (right - left) / 2;//中间元素的下标  
			if (nums[mid] = target)
			{
				return mid;
			}
			else if (nums[mid] > target)
			{
				right = mid - 1;
			}
			else if (nums[mid] < target)
			{
				left = mid + 1;
			}
		}
	}
};

青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

//青蛙跳台阶问题
class Solution
{
public:
	int numWays(int n)
	{
		vector<int>dp;//动态数组
		for (int i = 0; i <= n; i++)
		{
			if (i == 0 || i == 1)dp.push_back(1);
			else dp.push_back((dp[i - 1] + dp[i - 2]) % 1000000007);
			//第一次跳2级,此时跳法数目等于后面剩下n-2阶台阶的跳法数目,即dp[i-2];
			//第一次只跳1级,跳法数目等于后面剩下n-1阶台阶的数目,即dp[i-1];
		}
		return dp[n]; //dp[n]表示n级台阶的跳法数目;
	}
};



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.liang呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值