作为自己刷题的一个笔记记录,顺便复习一下C++的知识。
大概会按类似的算法思想总结,无法归类的题我就单独分一类了。
代码有的是自己写的,有的是力扣官方题解,有的是以下链接的题解。刷题顺序也是按以下链接的博主总结的顺序来写的。
【LeetCode & 剑指offer 刷题笔记】汇总(已完成)_wikiwen的博客-CSDN博客_leetcode 剑指offer
1.【哈希表】两数之和。
用哈希表,增加效率。遍历1遍数组,在哈希表中找target-num[i],找不到就把该数放入哈希表中,找到就返回num[i],target-num[i],【题号1】。
class Solution {
public:
vector twoSum(vector& nums, int target) {
unordered_map hashtable;
for(int i=0;isecond,i};
}
hashtable[nums[i]]=i;
}
return {};
}
};
变式1:两个数组的交集。
给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 题号:349
思路:将一个数组存储到set中,然后遍历另一个数组中的元素是否包含在set中,如果存在加入结果集合,并去除该数,避免重复
class Solution
{
public :
vector < int > intersection ( vector < int >& nums1 , vector < int >& nums2 )
{
if ( nums1 . empty () || nums2 . empty ()) return {};
unordered_set m(nums1.begin(), nums1.end()); //vector转set,避免重复
vector < int > res ;
for ( auto a : nums2 )
{
if ( m . count ( a ))
{
res . push_back ( a );
m . erase ( a ); //去除该数
}
}
return res ;
}
};
2.【双指针】有序数组,给定目标数,找两数和为该目标。
双指针,一个指针是最小的,另一个是最大的。【题号167】,【剑指offer57】
class Solution {
public:
vector twoSum(vector& numbers, int target) {
int left=0,right=numbers.size()-1;
while(lefttarget)
right--;
else
return {left+1,right+1};
}
return{};
}
};
变式1:搜索二叉树,先先序遍历获得其有序的结果,再用双指针。【题号653】
变式2:输入一个target,输出和为目标的所有连续正整数序列。比如t=9,返回[[2,3,4],[4,5]]。【剑指offer57】
class Solution {
public:
vector> findContinuousSequence(int target) {
vector> result;
int l=1,r=2;
while(ltarget)//该窗口的值大于目标值
l++; //左指针向右滑动
else{//该窗口值等于目标值
vector temp;
for(int i=l;i<=r;i++){
temp.emplace_back(i);//将该窗口值存储
}
result.emplace_back(temp);//将该数组存储
l++;//左指针向右滑动
}
}
return result;
}
};
变式3:3数之和,给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a ,b ,c ,使得 a + b + c = 0 ?请找出所有和为 0 且 不重复 的三元组。【剑指offer007】
class Solution {
public:
vector> threeSum(vector& nums) {
vector> ans;
sort(nums.begin(),nums.end()); //先排序
int n=nums.size();
for(int first=0;first0 && nums[first]==nums[first-1])//保证不重复处理相等的两个数
continue;
int target=-nums[first];//转换为两数之和问题,
int second=first+1;
int third=n-1;
while(secondfirst+1 && nums[second]==nums[second-1])
{
second++;
continue;
}
if(nums[second]+nums[third]target)
third--;
else
{ ans.push_back({nums[first],nums[second],nums[third]});
second++;
}
}
}
return ans;
}
};
变式4:最接近的三数之和。
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。返回这三个数的和。【题号16】
class Solution {
public:
int threeSumClosest(vector& nums, int target) {
int ans;
sort(nums.begin(),nums.end());//排序
int n=nums.size();
int dis=nums[0]+nums[n-1]+nums[n-2];//定义一个三数之和,作为最接近的和
for(int first=0;firsttarget)
{
third--;
}
else
{
return target;
}
}
}
return dis;//返回最近的距离
}
};
变式5:4数之和
给你一个由 n
个整数组成的数组 nums
,和一个目标值 target
。请你找出并返回满足下述全部条件且不重复的四元组。【题号18】
解决思路:先排序,参考三数之和,只不过加一层循环。
class Solution {
public:
vector> fourSum(vector& nums, int target) {
sort(nums.begin(),nums.end());
int n=nums.size()-1;
vector> res;
int t,left,right,sum;
for(int i=0;i0 && nums[i]==nums[i-1])//避免重复
continue;
for(int j=i+1;ji+1 && nums[j]==nums[j-1])//避免重复
continue;
//这两个判断提高了效率。如果这两种情况则找不到,直接跳出循环
if((long)nums[i]+nums[j]+nums[j+1]+nums[j+2] >target)
break;
if((long)nums[i]+nums[n-2]+nums[n-1]+nums[n] j+1 && nums[left]==nums[left-1])
{
left++;
continue;
}
if((long)nums[i]+nums[j]+nums[left]+nums[left+1] >target)
break;
if((long)nums[i]+nums[j]+nums[n-1]+nums[n]
3.【排序】找重复数字
给你一个整数数组 nums
。如果任一值在数组中出现 至少两次 ,返回 true
;如果数组中每个元素互不相同,返回 false 【题号217】
排好序,如果重复则会有相邻的两个数字相等。
class Solution {
public:
bool containsDuplicate(vector& nums) {
sort(nums.begin(),nums.end());
for(int i=0;i
变式1: 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。题号:39
class Solution {
public:
int majorityElement(vector& nums) {
sort(nums.begin(),nums.end());
int count=0;
for(int i=0;i=nums.size()/2)
return nums[i];
}
else
count=0;
}
return nums[0];
}
};
变式2:【排序+双指针】两个数组的交集。
给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 题号:349
class Solution {
public:
vector intersection(vector& nums1, vector& nums2) {
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
vector result;
int m=nums1.size(),n=nums2.size();
int k=0,p=0;
while(k0 && nums1[k]==nums1[k-1] )
{k++;continue;}
if(p>0 && nums2[p]==nums2[p-1] )
{p++;continue;}
if(nums1[k]nums2[p])
p++;
else
{
result.push_back(nums1[k]);
k++;p++;
}
}
return result;
}
};
4.【二分法】统计一个数字在排序数组中出现的次数。题号:53
因为有序,可以利用有序这一条件,使用二分法。找到第一次出现的位置,和最后一次出现的位置。
这个二分法的边界条件要把<,=,>分别写一下,然后进行合并归类。
class Solution {
public:
int findPosition(vector &nums,int target,bool flag)
{
int left=0,right=nums.size()-1,ans=-1;
while(left<=right)
{
int mid=(left+right)/2;
printf("mid =%d",mid);
if(nums[mid]& nums, int target) {
int left=findPosition(nums,target,0)+1;
// printf("left:%d",left);
int right=findPosition(nums,target,1);
//printf("right:%d",right);
if(left<=right && right
5.【旋转数组】给定一个数组,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。题号:189
法1【环状替换】:1到3,3到5.只需temp记录数即可。回到原点时候,从下一个数开始遍历。通过找规律发现,遍历圈数为k和n的最大公约数。关键在于,分析出轮转圈数gcd(n,k)。
因为从 0 开始不断遍历,最终又回到起点 0,这个过程走了a圈(a正为整数),每圈的长度为n。 这个过程中,走的每一步的长度为k,共走过了b个元素,所以走的总步长为bk,也即这a圈的长度,即a*n=b*k。 即 a*n 一定为 n,k 的公倍数。又因为我们在第一次回到起点时就要结束,因此a要最小,故a*n就是n,k的最小公倍数lcm(n,k) , 因此 b 就为lcm(n,k)/k,这说明从起点再次回到起点的过程中会访问到 lcm(n,k)/k 个元素。 为了访问到所有的元素,我们需要进行遍历的次数为,n/lcm(n,k)/k = n*k/lcm(n,k) = gcd(n,k)(最大公约数和最小公倍数的关系) 即需要遍历的次数为n和k的最大公约数。
class Solution {
public:
void rotate(vector& nums, int k) {
int n = nums.size();
k = k % n;
int count = gcd(k, n);//轮转最大公约数圈
for (int start = 0; start < count; ++start) {
int current = start;//记录当前的下标
int prev = nums[start];//记录当前要轮转的数
do {
int next = (current + k) % n;//记录要轮转到的位置
swap(nums[next], prev);//进行轮转,并记录下一个要轮转的数
current = next;//记录轮转后当前的下标
} while (start != current);//当轮转一圈时,结束这一圈
}
}
};
法2【数组翻转】:尾部的k个元素会移动到头部,剩下的元素会往后移动k mod n个位置。先将所有元素翻转。再将0到k mod n -1个元素翻转,再将kmodn到n的元素翻转。
class Solution {
public:
void reverse(vector& nums, int start, int end) {
while (start < end) {
swap(nums[start], nums[end]);
start += 1;
end -= 1;
}
}
void rotate(vector& nums, int k) {
k %= nums.size();
reverse(nums, 0, nums.size() - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.size() - 1);
}
};
6.汇总区间
给定一个 无重复元素 的 有序 整数数组 nums 。返回恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。 题号228
思路:找到区间起始点和结束点
class Solution {
public:
vector summaryRanges(vector& nums) {
vector result;
if(nums.size()==0)
return result;
int start=nums[0],end;
for(int i=0;i"+to_string(end));
if(i
7.构建乘积数组
给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。【剑指offer66】
B[i]的值可以看作上图的矩阵中每行的乘积。
先算下三角中的连乘,然后计算上三角连乘,把两部分乘起来。
class Solution
{
public:
vector multiply(const vector& A)
{
if(A.empty()) return vector();
vector B(A.size());
//计算下三角连乘
B[0] = 1;
for(int i = 1; i=1; j-- ) //j=n-1~1
{
temp *= A[j]; //计算连乘值
B[j-1] *= temp;
}
return B;
}
};
法2:计算该数左边的数和右边的数,然后将两部分乘起来。
class Solution {
public:
vector productExceptSelf(vector& a) {
int length = a.size();
// L 和 R 分别表示左右两侧的乘积列表
vector L(length, 0), R(length, 0);
vector answer(length);
// L[i] 为索引 i 左侧所有元素的乘积
// 对于索引为 '0' 的元素,因为左侧没有元素,所以 L[0] = 1
L[0] = 1;
for (int i = 1; i < length; i++) {
L[i] = a[i - 1] * L[i - 1];
}
// R[i] 为索引 i 右侧所有元素的乘积
// 对于索引为 'length-1' 的元素,因为右侧没有元素,所以 R[length-1] = 1
R[length - 1] = 1;
for (int i = length - 2; i >= 0; i--) {
R[i] = a[i + 1] * R[i + 1];
}
// 对于索引 i,除 a[i] 之外其余各元素的乘积就是左侧所有元素的乘积乘以右侧所有元素的乘积
for (int i = 0; i < length; i++) {
answer[i] = L[i] * R[i];
}
return answer;
}
};
8.调整数组顺序
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。【剑指offer21】
思路:按某规则调整数组位置,从前和后开始遍历,满足条件则移动指针,不满足则交换两个不满足条件的数,指针继续移动。直到相遇。
class Solution {
public:
vector exchange(vector& nums)
{
int i = 0, j = nums.size() - 1;
while (i < j)
{
while(i < j && (nums[i] & 1) == 1) i++;
while(i < j && (nums[j] & 1) == 0) j--;
swap(nums[i], nums[j]);
}
return nums;
}
};
9. 扑克牌中的顺子
从若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。【剑指offer61】
法1:排序+遍历
class Solution {
public:
bool isStraight(vector& nums) {
int count=0;
sort(nums.begin(),nums.end());
//最大减去最小的牌小于5,则顺子
for(int i=0;i nums[i]+1)
{
if(nums[i+1]-nums[i]-1<=count)
count-=nums[i+1]-nums[i]-1;
else
return false;
}
else
return false;
}
return true;
}
};
法2:最大-最小<5 则可以构成顺子
class Solution {
public:
bool isStraight(vector& nums) {
unordered_set m;
int min_=14,max_=0;
for(int num:nums)
{
if(num==0)
continue;
min_=min(min_,num);
max_=max(max_,num);
if(m.count(num))
return false;
else
m.insert(num);
}
return max_-min_<5;
}
};
10.把数组排成最小的数
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。【剑指offer45】
思路:重写compare函数,例如a="30",b="3",a+b
class Solution {
public:
static bool Compare(const string &a,const string&b) {
return a+b& nums) {
vector
11.数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。【剑指offer51】
思路:归并排序,把两个有序数组合并,在归并过程中,计算逆序对。
class Solution {
public:
int reversePairs(vector& nums) {
int len=nums.size();
if(len<2)
return 0;
vector temp(len);
return reverse(nums,0,len-1,temp);
}
int reverse(vector& nums,int left,int right,vector& temp)
{
if(left==right)
return 0;
int mid=left+(right-left)/2;
int leftNum=reverse(nums,left,mid,temp);//左边的逆序对
int rightNum=reverse(nums,mid+1,right,temp);//右边的逆序对
if(nums[mid]<=nums[mid+1])//如果排好的两部分合在一起是有序的
{return leftNum+rightNum;}
int crossNum=cross(nums,left,mid,right,temp);//合并时的逆序对
return leftNum+rightNum+crossNum;
}
int cross(vector& nums,int left,int mid,int right,vector& temp)
{
for(int i=left;i<=right;i++)
{temp[i]=nums[i];}
int i=left,j=mid+1,count=0;
for(int k=left;k<=right;k++)
{
if(i==mid+1)
{//如果前部分数组已经归并完,直接合并另一个数组剩下的部分
nums[k]=temp[j];
j++;
}
else if(j==right+1)
{//如果后部分数组已经归并完,直接合并另一个数组剩下的部分
nums[k]=temp[i];
i++;
}
else
{ if(temp[i]<=temp[j])
{
nums[k]=temp[i];
i++;
}
else
{//出现逆序对,count计算逆序对个数
nums[k]=temp[j];
j++;
count+=(mid-i+1);
}
}
}
return count;
}
};
12.递增的三元子序列
给定一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 【题号334】 贪心方法
class Solution {
public:
bool increasingTriplet(vector& nums) {
int a=INT_MAX,b=a;
//a是最小的数,b是中间的数,因为先判断是否小于a,所以a是最小的,b是大于a的第二小的数。
//如果有比a和b大的数,那么就找到了递增的三元组,可以返回true
for(int num:nums)
{
if(num<=a)
{
a=num;
}
else if( num<=b)
{
b=num;
}
else
return true;
}
return false;
}
};
13.加一
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。【题号66】
思路比较简单 在代码的注释中写了。
class Solution {
public:
vector plusOne(vector& digits) {
int n=digits.size()-1;
int carry=1;
for(int i=n;i>=0;i--)
{ //从个位往最高位加
if(digits[i]==9)
{//如果该位是9,赋值0,并使carry为1,下一次进位
digits[i]=0;
carry=1;
}
else
{//不为9,加carry,进位或者最开始时候加1.并使carry为0
digits[i]+=carry;
carry=0;
return digits;
}
}
if(carry==1)//如果最高位为9.则申请一块新空间,最高位赋值1,其他位为0
{
vector ans(n + 2);
ans[0] = 1;
return ans;
}
else
return digits;
}
};
14.移动零
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操作。题号283
class Solution {
public:
void moveZeroes(vector& nums) {
int flag=0,p,s;
for(int i=0;i
15.有效的数组
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。【题号36】
每个数属于哪个box就只取决于纵坐标,纵坐标为0/1/2的都属于box[0],纵坐标为3/4/5的都属于box[1],纵坐标为6/7/8的都属于box[2].也就是j/3.
而对于9x9的矩阵,我们光根据j/3得到0/1/2还是不够的,可能加上一个3的倍数,例如加0x3,表示本行的box,加1x3,表示在下一行的box,加2x3,表示在下两行的box, 这里的0/1/2怎么来的?和j/3差不多同理,也就是i/3。作者:liujin-4
链接:https://leetcode.cn/problems/valid-sudoku/solution/36-jiu-an-zhao-cong-zuo-wang-you-cong-shang-wang-x/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
bool isValidSudoku(vector>& board) {
int row[9][9]={0};
int col[9][9]={0};
int box[9][9]={0};
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
{
if(board[i][j]!='.')
{ int index=board[i][j]-'0'-1;
if(row[i][index] || col[j][index] || box[(i/3)*3+j/3][index])
return false;
row[i][index]=1;
col[j][index]=1;
box[(i/3)*3+j/3][index]=1;
}
}
return true;
}
};
16.旋转图像
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
上下翻转:(i,j)->(n-i-1,j)
左右翻转:(i,j)->(i,n-j-1)
主对角线翻转:(i,j)->(j,i)
副对角线翻转:(i,j)->(n-j-1,n-i-1)
顺时针旋转90度(先上下翻,再主对角线翻):(i,j)->(j,n-i-1)
逆时针旋转90度(先左右翻,再副对角线翻):(i,j)->(n-j-1,i)
class Solution {
public:
void rotate(vector>& matrix) {
int n=matrix.size();
//上下翻转
for(int i=0;i