文章目录
- 数组中出现次数超过一半的数字 `哈希表的经典应用`
- 剑指offer 11 旋转数组的最小数字
- leetcode 33 搜索旋转排序数组 I
- 判断一个字符串s1是不是包含另一个字符串s2
- leetcode 151 颠倒字符串中的单词 (反转单词顺序)
- leetcode 165 比较版本号 M
- 反转字符串
- 对每隔2k元素的前k个元素进行反转
- 移除数组中的指定元素
- 反转链表
- 反转链表 II
- leetcode 328 奇偶链表
- 二叉搜索树与双向链表
- leetcode 143 重排链表 & 剑指offer II 026 重排链表
- leetcode 130 逆波兰表达式求值
- leetcode 242有效的字母异位词E && leetcode 49 字母异位词分组M
- 二分查找的写法
- 青蛙跳台阶问题
数组中出现次数超过一半的数字 哈希表的经典应用
这里要求的是出现次数超过一半 ,如何指定出现的次数为具体的值,那么只需将代码中 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级台阶的跳法数目;
}
};