数组篇
1.从排序数组中删除重复项(leecode第26题)
题目要求:给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
算法说明:简单题,因为要求空间复杂度O(1),不能额外创建数组,可以创建个变量res保存不一样的数的长度。
1.1利用vector类的方法(个人的)
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size()==0)
return 0;
int temp=nums[0];
int res=1;
vector<int>::iterator itor;
for(itor=nums.begin()+1;itor!=nums.end();)
{
if(*itor==temp)
itor=nums.erase(itor);
else
{
temp=*itor;
itor++;
res++;
}
}
return res;
}
};
这里主要是学习使用了vector类的迭代器iterator和删除函数erase(iterator it),erase返回的是删除的元素的下一个元素迭代器位置。
具体查看这里C++ vector容器简介.
也可以参考别人的初级算法题解
1.2双指针法
数组完成排序后,我们可以放置两个指针 i 和 j,其中 i是慢指针,而 j 是快指针。只要 nums[i]=nums[j],我们就增加 j 以跳过重复项。
当我们遇到 nums[j]≠nums[i] 时,跳过重复项的运行已经结束,因此我们必须把它 nums[j] 的值复制到 nums[i+1]。然后递增 i,接着我们将再次重复相同的过程,直到 j到达数组的末尾为止。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size()==0)
return 0;
int i=0;
for(int j=0;j<nums.size();j++)
{
if(nums[j]!=nums[i])
{
i++;
nums[i]=nums[j];
}
}
return i+1;
}
};
2.买卖股票的最佳时机II(leecode第122题)
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
2.1暴力法
这种情况下,我们只需要计算与所有可能的交易组合相对应的利润,并找出它们中的最大利润。利用迭代的方法
class Solution {
public:
int maxProfit(vector<int>& prices) {
return calculate(prices,0);
}
int calculate(vector<int>& prices,int s){
if(s>=prices.size())
return 0;
int max=0;
for(int start=s;start<prices.size();start++)
{
int maxprofit=0;
for(int end=start+1;end<prices.size();end++)
{
if(prices[start]<prices[end])
{ // 迭代求出profit为每一个组合的利润
int profit=calculate(prices,end+1)+prices[end]-prices[start];
if(profit>maxprofit)
maxprofit=profit;
}
}
if(maxprofit>max)
max=maxprofit;
}
return max;
}
};
该方法运行时间过长,迭代次数太多了,时间复杂读O(nn)
2.2贪心法
算法:只要i+1天比i天价格高就卖出,累加就能得出结果了
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
int maxprofit=0;
for(int i=0;i<n-1;i++)
if(prices[i+1]>prices[i])
maxprofit+=prices[i+1]-prices[i];
return maxprofit;
}
};
3.旋转数组(leecode第189题)
题目:给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
3.1循环交换法
循环k次,每次都将数组向右移动一次
class Solution {
public:
void rotate(vector<int>& nums, int k) {
k%=nums.size();
for(int i=0;i<k;i++)
{
int temp=nums[nums.size()-1];
for(int j=nums.size()-1;j>0;j--)
swap(nums[j],nums[j-1]);
nums[0]=temp;
}
}
};
程序中用到c++提供的swap函数用于交换, swap 包含在命名空间std 里面,还能交换结构体。
3.2反转法
该思想是将整个数组反转一次,再将1-k反转一次,k-n反转一次就能得到结果
举例如k=3,[1,2,3,4,5,6,7]---->[7,6,5,4,3,2,1]
[7,6,5]–>[5,6,7]
[4,3,2,1]–>[1,2,3,4]结果就变成[5,6,7,1,2,3,4]
不过该方法要写一个反转函数
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n=nums.size();
k=k%n;
if(n==1)
return;
inverse(nums,0,n-1);
inverse(nums,0,k-1);
inverse(nums,k,n-1);
}
void inverse(vector<int>& nums,int start,int end)
{
while(start<end)
{
swap(nums[start],nums[end]);
start++;
end--;
}
}
};
该方法时间复杂度O(n),空间复杂度O(1).
4.存在重复数(leecode第217题)
题目:给定一个整数数组,判断是否存在重复元素。
如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。
4.1 set容器法
算法:这题很简单,刚开始我直接暴力法解决的,但是测试用例较大运行时间过长,没通过就不写了,这里学习使用了c++ STL模板库中set关联容器,把数组的元素insert到set容器,insert前判读容器中是否已经存在,存在的话返回true,所有元素insert完返回false。
也有人用的是unordered_set 容器是c++ 11新加入容器,set是通过RB-tree实现,unordered_set是通过哈希表实现的
不懂set的用法可以参考set的具体用法
程序
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
int n=nums.size();
set<int> temp;
for(int i=0;i<n;i++)
if(temp.count(nums[i])==0)
temp.insert(nums[i]);
else
return true;
return false;
}
};
5 只出现一次的数(leecode第136题)
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
本题是面试原题,难度在于不能用额外空间和线性时间复杂度(就是时间复杂度为O(n)),一般使用的是异或的方法
异或(^):1与1,0与0异或为0(相同即为假,不同则为真)
注:
- A.相同的数异或为0,
- B.任何数与0异或为任何数
- C.异或满足交换律
class Solution {
public:
int singleNumber(vector<int>& nums) {
int m=0;
for(int i=0;i<nums.size();i++)
m=m^nums[i];
return m;
}
};
6 两个数组的交集II(leecode第350题)
题目:给定两个数组,编写一个函数来计算它们的交集。
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致,我们可以不考虑输出结果的顺序。
进阶:
如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
6.1排序法
如果已经排好序,我们可以通过用一个for循环从小到大来比较,如果其中一个数组的数比另一个小了,将这个数组下标+1再判断,如果两个值相同,下标都加一,且将该数字保存到结果数组中。
class Solution {//还有点问题,运行不了
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(),nums2.end());
sort(nums2.begin(),nums2.end());
vector<int> res;
int n1=nums1.size();
int n2=nums2.size();
for(int i=0,j=0;i<n1 && j<n2;)
{
if(nums1[i]>nums2[j])
j++;
else if (nums1[i]<nums2[j])
i++;
else if(nums1[i]==nums2[j])
{
res.push_back(nums1[i]);
i++;
j++;
}
}
return res;
}
};
6.2 set交集法
C++ STL 提供求交集的函数 set_intersection( ) 、求集合差的函数 set_difference( ) 和合并两个集合的函数 set_union( ) 。这里使用了set_intersection()求两个数组交集,线性时间复杂度,set_intersection( ) 函数用法链接
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int>res;
//set_intersection 传入的容器必须是有序的
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
set_intersection(nums1.begin(),nums1.end(),nums2.begin(),nums2.end(),back_inserter(res));
return res;
}
};
6.3哈希法
用map映射容器建立哈希表的方法,先用nums1数组建立哈希表,然后用nums2数组来查找重复的数,输出到结果中,并将个数减一
map<int,int> stable;//第一个int为数组的值,第二个为值的个数
程序:
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
map<int,int> table;
vector<int> res;
for(int i=0;i<nums1.size();i++)//建立哈希表
table[nums1[i]]++;
for(int i=0;i<nums2.size();i++)
if(table[nums2[i]]>0) {res.push_back(nums2[i]);table[nums2[i]]--;}
return res;
}
};
7 加一(leecode第66题)
题目:给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
7.1 转换法
将数组表示的值算出存在单个int变量中,加一后再将各位数字拆分放在结果数组中,但是该方法所能表示的数不能超过int的存储范围,在leecode上未通过由于测试用例数据太大。
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int n=digits.size();
int value=0,i=0;
int end,start;
vector<int> res;
while(n>0)
{
value+=(int)(digits[i]*pow(10,n-1));
i++;n--;
}
value++;
while(value>10)
{
res.push_back(value%10);
value/=10;
}
res.push_back(value);
end=res.size()-1;
start=0;
while(start<end)
{
swap(res[start],res[end]);
start++;
end--;
}
return res;
}
};
7.2 进制法
该方法是设置一个进位标志count,加一,所以count初始化为1,遍历数组每一个元素,如果count=1,将该位加一,且加一是否大于10来改变count值,否则不处理,,如果最后一位count还为1,必须新push_back一个1到首位,将改变的digits输出到结果里。
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int n=digits.size();
vector<int> res;
int count=1;
for(int i=n-1;i>=0;i--)
if(count==1)
if(digits[i]+1>9)
{
digits[i]=0;
count=1;
}
else
{
digits[i]=digits[i]+1;
count=0;
}
if(count==1)
res.push_back(1);
for(int i=0;i<n;i++)
res.push_back(digits[i]);
return res;
}
};
8 移动零(leecode第283题)
题目:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
算法:该方法是将不是零的数放在前面,后面补零。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int index=0;
for(int i=0;i<nums.size();i++)
if(nums[i]!=0)
{
nums[index]=nums[i];
index++;
}
for(int i=index;i<nums.size();i++)
nums[i]=0;
}
};
9.两数之和(leecode第1题)
题目:给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
9.1暴力法
直接用所有元素遍历查找就能得出结果
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> res;
for(int i=0;i<nums.size();i++)
for(int j=i+1;j<nums.size();j++)
if(target==nums[i]+nums[j])
{
res.push_back(i);
res.push_back(j);
break;
}
return res;
}
};
注:返回其实可以不用向量,直接返回。
if(target==nums[i]+nums[j])
return {i,j};
return {i,j};
9.2两遍哈希法
可以通过哈希表来查找,减少时间复杂度,本程序通过map容器建立哈希表,key存数组值,元素存数组位置。利用map的count函数判断是否有这个元素。时间复杂度,空间复杂度都是O(n)。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int,int> table;
vector<int> res;
for(int i=0;i<nums.size();i++)
table[nums[i]]=i;
for(int i=0;i<nums.size();i++)
if(table.count(target-nums[i])>0 && table[target-nums[i]]!=i )
{
res.push_back(i);
res.push_back(table[target-nums[i]]);
break;
}
return res;
}
};
其实如果哈希表建立好后,可以用iterator迭代器返回位置。
map <int,int>:: iterator it;
it=table.find(target-nums[i]]);//it 保存的是元素位置,find没找到会返回end的位置
if(it!=table.end() && it->second!=i)
{
res.push_back(i);
res.push_back(table[target-nums[i]]);
break;
}
注:it->second 是元素的值,it->first 是key的值。
9.3一遍哈希法
其实上个方法相似,时间复杂度,空间复杂度也都是O(n)。只不过在创建哈希表的时候就把结果查找出来。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int,int> table;
vector<int> res;
for(int i=0;i<nums.size();i++)
{
if(table.count(target-nums[i])>0)
{
res.push_back(table[target-nums[i]]);
res.push_back(i);
break;
}
table[nums[i]]=i;
}
return res;
}
};
10.有效的数独(leecode第36题)
题目:判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
- 数字 1-9 在每一行只能出现一次。
- 数字 1-9 在每一列只能出现一次。
- 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
- 空白格用 ‘.’ 表示。
示例 1:
输入:
[
[“5”,“3”,".",".",“7”,".",".",".","."],
[“6”,".",".",“1”,“9”,“5”,".",".","."],
[".",“9”,“8”,".",".",".",".",“6”,"."],
[“8”,".",".",".",“6”,".",".",".",“3”],
[“4”,".",".",“8”,".",“3”,".",".",“1”],
[“7”,".",".",".",“2”,".",".",".",“6”],
[".",“6”,".",".",".",".",“2”,“8”,"."],
[".",".",".",“4”,“1”,“9”,".",".",“5”],
[".",".",".",".",“8”,".",".",“7”,“9”]
]
输出: true
说明:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 ‘.’ 。
给定数独永远是 9x9 形式的。
10.1集合法
解题思路:我是用set容器通过集合的方式解决的,首先创建行,列,块各九个集合,对81个元素进行遍历,如果字符不是’ . ',就将字符插入对应容器,插入前先判断是否已经存在该字符,存在返回false,否则插入。遍历完后返回true。
行列集合好分类,块的分类参考了官方的题解,如下:
block=(row/3)*3+column/3;
程序:
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
set<char> row[9],column[9],block[9];
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
{
if(board[i][j]!='.')
{
if(row[i].count(board[i][j])==0)
row[i].insert(board[i][j]);
else
return false;
if(column[j].count(board[i][j])==0)
column[j].insert(board[i][j]);
else
return false;
if(block[(i/3)*3 +j/3].count(board[i][j])==0)
block[(i/3)*3 +j/3].insert(board[i][j]);
else
return false;
}
}
return true;
}
};
11.旋转图像(leecode第48题)
题目:给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
11.1 整体反转法
解题思路:
- 先将方阵按(\)左上到右下对角的反转,再垂直反转,或者
- 先垂直反转,再将方阵按(/)左下到右上对角的反转,或者
- 先水平反转,再将反正按(\)左上到右下对角的反转,或者
- 先将方阵按(/)左下到右上对角的反转,再水平反转。
这四种方式都可以得到顺时针旋转90°的效果,不太理解可以用个方片在手里模拟一下。
程序:
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n=matrix.size();
for(int i=0;i<n;i++)
for(int j=0;j<i;j++)
swap(matrix[i][j],matrix[j][i]);
for(int i=0;i<n;i++)
for(int j=0;j<n/2;j++)
swap(matrix[i][j],matrix[i][n-j-1]);
}
};
11.2分圈旋转法
解题思路:可以从外圈到内圈一圈圈旋转,每一圈可以看成四个边组成,每个边有n-1个数。
a b----> d a // a,b,c,d分别代表每个边
c d----> c b
a和b换,a和d换,a和c换,就是顺时针旋转90°。
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n=matrix.size();
for(int i=0;i<n/2;i++)
for(int j=i;j<n-1-i;j++)
{
swap(matrix[i][j],matrix[j][n-i-1]);
swap(matrix[i][j],matrix[n-i-1][n-j-1]);
swap(matrix[i][j],matrix[n-j-1][i]);
}
}
};
数组篇到此结束。