1.两数之和
题目:给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
方法一:暴力破解
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
for(int i=0;i<nums.size();i++)
{
for(int j=i+1;j<nums.size();j++)
{
if (nums[i]+nums[j]==target)
{
return{i,j};
}
}
}
return{};
}
};
方法二:哈希表
当要判断该元素是否出现过,就考虑使用哈希表
可以使用数组和set作哈希表的映射,但是在该题中需要存放元素以及其下标,使用map数据结构
map: key--->元素,value--->下标
因为需要查找元素是否出现过,因此,将元素作为key
map: map、unordered_map、multi_map 13是实现红黑树,2是哈希结构,进行映射
本题使用2,存读速率最快
class Solution {
public int[] twoSum(int[] nums, int target) {
// 创建一个哈希表,用于存储数组元素及其下标
// 键:数组中的元素值,值:该元素在数组中的下标
Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
// 遍历数组中的每个元素
for (int i = 0; i < nums.length; ++i) {
// 计算当前元素的互补数,即 target 减去当前元素的值
// 如果存在另一个元素等于互补数,则两数之和为 target
int complement = target - nums[i];
// 检查哈希表中是否已存在互补数
if (hashtable.containsKey(complement)) {
// 如果存在,说明找到了两个数的和为 target
// 返回互补数的下标(存储在哈希表中)和当前元素的下标 i
return new int[]{hashtable.get(complement), i};
}
// 如果哈希表中不存在互补数
// 将当前元素的值及其下标存入哈希表,供后续查找使用
// 注意:这一步必须放在检查之后,以避免重复使用同一个元素
hashtable.put(nums[i], i);
}
// 如果遍历完整个数组都没有找到符合条件的两个数
// 返回一个空数组表示无解
return new int[0];
}
}
2.两数相加
题目:给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807.
分析:2+5=7,没有进位,直接利用尾插法插入新链表;
4+6=10,两位数,有进位
(1)取个位:利用取模运算,10%10=0 ,直接插入新链表
(2)取进位值:利用除法运算,10/10=1,存入进位,累加到下一个数
3+4=7,还要加上前面的进位
前提条件:l1 l2不为空
使用迭代法,total=l1.val+l2.val+next(进位);
取个位:total%10
取进位:total/10
前提条件:l1 不为空,l2为空
赋l2.val=0,其余不变
前提条件:l2 不为空,l1为空
赋l1.val=0,其余不变
前提条件:都为空,但进位值为1
存入最后一个结点,值为1
代码:
int total=0;
int next=0; //初始化
ListNode *result=new ListNode(0); //创建一个虚拟头结点
ListNode *cur=result; //当前结点指针,使用指针对后续值进行改变
while(l1!=NULL&&l2!=NULL)
{
total=l1->val+l2->val+next;
l1=l1->next; //指向下一个
l2=l2->next; //指向下一个
cur->next=new ListNode(total%10); //将结果链表的指针指向 创建的一个结点(这个结点存放的是两数之和)
cur=cur->next; //移动当前结点指针
next=total/10; //next存放进位
}
while(l1!=NULL&&l2==NULL) //l2遍历完了
{
total=l1->val+next; //不再加l2的值
l1=l1->next; //指向下一个
cur->next=new ListNode(total%10); //将结果链表的指针指向 创建的一个结点(这个结点存放的是两数之和)
cur=cur->next; //移动当前结点指针
next=total/10; //next存放进位
}
while(l2!=NULL&&l1==NULL) //l1遍历完了
{
total=l2->val+next; //不再加l1的值
l2=l2->next; //指向下一个
cur->next=new ListNode(total%10); //将结果链表的指针指向 创建的一个结点(这个结点存放的是两数之和)
cur=cur->next; //移动当前结点指针
next=total/10; //next存放进位
}
if (next==1)
{
cur->next=new ListNode(1); //存入进位
cur=cur->next; //移动当前结点指针
}
return result->next; //返回结果链表的头结点
简化代码:
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int total=0;
int next=0; //初始化
ListNode *result=new ListNode(0); //创建一个虚拟头结点
ListNode *cur=result; //当前结点指针,使用指针对后续值进行改变
while(l1!=NULL||l2!=NULL) //只有当二者都为空,才退出循环
{
int a;
int b;
if(l1==NULL)
a=0;
else
a=l1->val;
if(l2==NULL)
b=0;
else
b=l2->val;
total=a+b+next;
if(l1!=NULL)
l1=l1->next; //指向下一个
if(l2!=NULL)
l2=l2->next; //指向下一个
cur->next=new ListNode(total%10); //将结果链表的指针指向创建的一个结点(存放的是两数之和)
cur=cur->next; //移动当前结点指针
next=total/10; //next存放进位
}
if (next==1)
{
cur->next=new ListNode(1); //存入进位
cur=cur->next; //移动当前结点指针
}
return result->next; //返回结果链表的头结点
}
};
3. 无重复字符的最长子串
给定一个字符串 s
,请你找出其中不含有重复字符的 最长 子串 的长度。
示例 1:
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3
思路分析:
遍历字符串,需要判断字符是否出现过,这就想到了第一题官方题解使用的哈希表
通过依次从第一位、第二位.(i)..开始遍历,若在哈希表中不存在,则将字符放入哈希表,若存在则将当前哈希表的大小数值存入result数组中,然后将哈希表清空,从下一位开始遍历,直至遍历完整个字符串
取数组的最大值即为结果
class Solution {
public:
int lengthOfLongestSubstring(string s) {
vector<int> results; // 存放多个结果
unordered_map<char, int> hashTable; // 创建hash表,数据类型是char字符型
if (s.length() == 0) {
return 0; //""
} else if (s.length() == 1) {
return 1; //" " //"a".......
} else if (s.length() > 1) {
for (int i = 0; i < s.length(); i++) {
for (int j = i; j < s.length(); j++) {
if (hashTable.find(s[j]) ==
hashTable
.end()) // 不在哈希表,find()方法只能查找键(key)不能直接查找值
hashTable.insert({s[j], 1}); // 插入
else {
results.push_back(hashTable.size());
hashTable.clear();
break;
}
}
}
}
int maxVal = *max_element(results.begin(), results.end());
return maxVal;
}
};
使用set的题解
1.不能存放相同元素,使用集合set这一数据结构,并且只需要一个参数:字符,刚才的哈希值需要键和值;
2.使用左右指针:左指针在每个字符串的开头,右指针向右滑动,取字符,若能放入set,表明没有重复,放入set;若不能放入,说明重复,左右指针间的长度就是当前字符串的长度;然后取出左指针的字符,左指针再+1,右指针再滑动,重复该步骤;
3.定义length和 max_length:每次指针滑动(不重复),length就+1,+1后与max_length比较,若小于max_length,max_length不变;若大于max_length,max_length更新为当前length (通过设置两个值,来找最大数,大于就更新)
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_set<char> occ; // 定义集合,用来判断是否出现过
int length=0;
int maxlength=0;
int j=0;//右指针
for (int i = 0; i < s.length(); i++) { //左指针
while(j<s.length())
{
if (occ.count(s[j]) == 0) { //如果集合里没有出现过
occ.insert(s[j]); //插入该值
j++; //右指针移动
length++; //长度+1
if (length >= maxlength)
maxlength =length; //获取最大长度
} else { //集合中出现过
length--; //左指针之后要移动,提前将长度-1
occ.erase(s[i]); //删除左指针对应的字符
break; //跳出内循环,左指针移动
}
}
}
return maxlength;
}
};
4. 寻找两个正序数组的中位数
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n))
。
题解一:空间复杂度O(m+n)
分析:
1.先对数组排序,使用vector迭代器,直接使用merge和sort()方法对两个数组进行合并以及排序
2.偶数个数的中位数是中间两个数相加再除以2 长度/2 长度/2+1
奇数个数的中位数是中间那个数 长度/2+1
3.在迭代器中,取第n个数利用起始迭代器,移位进行寻找第n个,代码如下:
auto it = result.begin(); //起始迭代器 advance(it, n-1); //寻找第n个数
完整代码:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
vector<int> result(nums1.size() + nums2.size());
merge(nums1.begin(),nums1.end(), nums2.begin(),nums2.end(), result.begin()); //合并
sort(result.begin(),result.end()); //排序
auto it = result.begin();
advance(it, 1);
if (result.size()%2==1)
{
auto it = result.begin();
advance(it, result.size()/2);
return *it;
}
else{
auto a=next(result.begin(), result.size()/2-1);
auto b=next(result.begin(), result.size()/2);
return (float(*a)+float(*b))/2;
}
}
};
题解二(官方题解):空间复杂度O(log (m+n))
以下思路及图片来自官方题解
分析:
1.竖线分割
对于两个数组,可以使用两条竖线进行分割
对于个数和为偶数的两个数组:两条竖线左边的最大值和右边的最小值之和/2 就是中位数
对于个数和为偶数的两个数组:两条竖线左边的最大值 就是中位数 (前提是竖线左边的个数是(总数+1)/2,也可以是右边的最小数)
2.怎么分割竖线?
(1)竖线左边的大小
对于偶数个数和:左边大小为(m+n)/2 =(m+n+1)/2 (因为在运算中,/向下取整)
对于奇数个数和:左边大小为(m+n+1)/2
(2)竖线要满足的关系
第一个数组左边的最大值要 小于 第二个数组右边的最小值
第二个数组左边的最大值要 小于 第一个数组右边的最小值
这样才能保证:竖线左边的值都小于右边的值
(3)两个极端情况
a.竖线在较短数组的左/右没有元素
竖线在较短的数组左边没有元素
竖线在较短的数组右边没有元素
因此,应该在较短的数组上确定竖线的位置
b.对于两个长度相同的数组,竖线在数组的左/右没有元素
代码:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
// 优化:确保nums1是较短的数组,减少二分查找的范围
if (nums1.size() > nums2.size()) {
return findMedianSortedArrays(nums2, nums1);
}
int m = nums1.size(); // 较短数组的长度
int n = nums2.size(); // 较长数组的长度
int left = 0, right = m; // 二分查找的左右边界
// median1:合并后数组左半部分的最大值
// median2:合并后数组右半部分的最小值
int median1 = 0, median2 = 0;
// 二分查找在nums1中合适的分割点
while (left <= right) {
// 在nums1中选取分割点i,将nums1分为两部分
int i = (left + right) / 2;
// 在nums2中选取分割点j,使得i+j = (m+n+1)/2
// 这样左半部分的元素个数等于或比右半部分多一个
int j = (m + n + 1) / 2 - i;
// 处理边界情况,获取分割点左右的元素值
// 如果分割点在数组边界,使用INT_MIN或INT_MAX作为哨兵值
int nums_im1 = (i == 0 ? INT_MIN : nums1[i - 1]); // nums1左半部分的最大值
int nums_i = (i == m ? INT_MAX : nums1[i]); // nums1右半部分的最小值
int nums_jm1 = (j == 0 ? INT_MIN : nums2[j - 1]); // nums2左半部分的最大值
int nums_j = (j == n ? INT_MAX : nums2[j]); // nums2右半部分的最小值
// 检查分割是否正确:左半部分的所有元素都应小于等于右半部分的所有元素
if (nums_im1 <= nums_j) {
// 分割正确,更新中位数候选值
median1 = max(nums_im1, nums_jm1); // 左半部分的最大值
median2 = min(nums_i, nums_j); // 右半部分的最小值
left = i + 1; // 尝试在右半部分寻找更好的分割点
} else {
// 分割不正确,需要将i向左移动
right = i - 1;
}
}
// 根据总元素个数的奇偶性返回中位数
// 如果总元素个数为偶数,返回左右两部分的平均值
// 如果总元素个数为奇数,返回左半部分的最大值
return (m + n) % 2 == 0 ? (median1 + median2) / 2.0 : median1;
}
};