leedcode 初级算法 数组

链接

删除排序数组中的重复项

题目说明

给你一个有序数组nums请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组 并在使用 O(1) 额外空间的条件下完成。

题目解析

使用两个索引,i和j
i指向基元素
j指向比较元素
若两个元素不相等的话就说明中间与元素i相等的元素全部略过了,之后只要将元素j的值赋予元素i的下一个元素就行。
别忘了更新索引的值。
最后结果就是i的值加一。
本题不需要删除多余重复的额元素。
由0到i就是得到新的数组。

ac代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int i=0;
int j=0;
int n=nums.size();
if(n==0)
return 0;
while(j<=n-1)
{
if(nums[j]!=nums[i])
{
nums[++i]=nums[j];
}
j++;
}
return ++i;
}
};

买卖股票的最佳时机

题目说明

给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

题目解析

贪心,就和骑自行车上坡一样,怎样最费力,就是抓住每一个上坡的机会去上坡。当然也可以倒着来就是从后往前抓住每一个可以下坡的机会进行下坡。

ac代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using namespace std;
class Solution {
public:
int maxProfit(vector<int>& prices) {
int i=0;
int count=0;
for(i=prices.size()-1;i>=1;i--)
{
if(prices[i-1]<prices[i])//买入
count+=prices[i]-prices[i-1];
}
return count;
}
};

旋转数组

题目说明

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

题目解析 方法一

将k mod n 变为实际需要移动的次数,然后循环右移k次即可。
但是这样时间复杂度太高了。
代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n=nums.size();
k=k%n;
if(k==0)
return;
int temp=0;
for(int i=1;i<=k;i++)//右移k个单位
{
for(int j=n-1;j>=0;j--)
{
if(j==n-1)
temp=nums[j];
else
{
nums[j+1]=nums[j];
}
}
nums[0]=temp;
}
}
};
题目解析 方法二

和方法一类似都是移动元素,只不过这个是一步到位的移动,时间复杂度相比来讲小了不少,但是怎样保证不重复移动是一问题。设n与k的最大公约数为m,
则只移动前m个元素就不会重复移动了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n=nums.size();
k=k%n;
if(k==0)
return;
//找到k与n的最大公约数m
int m=0;
for(m=k;m>=1;m--)
{
if(k%m==0&&n%m==0)
break;
}
for(int i=0;i<=m-1;i++)
{
//对于这些元素开头的元素序列进行轮换。
int prev=i;
int next=(prev+k)%n;
int temp1=nums[i];
int temp2=temp1;
do{
temp2=nums[next];
nums[next]=temp1;
//nums[prev]=temp1;
temp1=temp2;
prev=next;
next=(prev+k)%n;
}while(prev!=i);//这轮元素又回到头了

}
}
};
题目解析 方法三

该方法基于如下的事实:当我们将数组的元素向右移动 kk 次后,尾部 k\bmod nkmodn 个元素会移动至数组头部,其余元素向后移动 k\bmod nkmodn 个位置。该方法为数组的翻转:我们可以先将所有元素翻转,这样尾部的 k\bmod nkmodn 个元素就被移至数组头部,然后我们再翻转 [0, (k mod n) -1]区间的元素和 [(k mod n), n-1]区间的元素即能得到最后的答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using namespace std;
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n=nums.size();
k=k%n;
if(k==0)
return;
rotate1(nums,0,n-1);
rotate1(nums,0,k-1);
rotate1(nums,k,n-1);
}
void rotate1(vector<int>& nums, int begin, int end)
{
for(int i=begin;i<=(begin+end)/2;i++)
{
int sum=begin+end;
int temp=nums[i];
nums[i]=nums[sum-i];
nums[sum-i]=temp;
}
}
};

存在重复元素

题目说明

给定一个整数数组,判断是否存在重复元素。如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。

题目解析 方法一

排序后遍历数组如果存在某一个数与其后面的数相等则说明重复的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {

int n=nums.size();
if(n==0||n==1)
return false;
sort(nums.begin(),nums.end());

for(int i=0;i<=nums.size()-2;i++)
{
if(nums[i]==nums[i+1])
return true;
}
return false;
}
};
题目解析 方法二

使用哈希表一个一个寻找和插入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_set<int> s; //#include <unordered_set>
for(int x:nums)
{
if(s.find(x)!=s.end())
{
return true;
}
s.insert(x);
}
return false;
}
};

只出现一次的数字

题目说明

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

题目解析 方法一

排序数组,遍历数组依次加减,所得就是答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int singleNumber(vector<int>& nums) {
sort(nums.begin(),nums.end());
bool flag=true;
int sum=0;
for(int x:nums)
{
if(flag)
{
flag=false;
sum+=x;
}
else
{
flag=true;
sum-=x;
}
}
return sum;
}
};

题目解析 方法二

遍历数组,使用集合存储数字,若由则删除,没有则存储。最后剩下的就是所要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_set<int> s;
for(int x:nums)
{
if(s.find(x)!=s.end())
{
s.erase(x);
}
else
s.insert(x);
}
for(int x:s)
return x;
return 0;
}
};
题目解析 方法三

使用哈希表存储每个数字和该数字出现的次数。遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_map<int,int> s;
for(int x:nums)
{
if(s.find(x)!=s.end())
{
s.erase(x);
s.insert(x,2);
}
else
{
s.insert(x,1);
}
}
int num=0;
unordered_map<int,int>::iterator x;
for(x=s.begin();x!=s.end();x++)
{
if(x->second==1)
{
num=x->second;
break;
}
}
return num;
}
};
题目解析 方法四

异或运算

1
2
3
4
5
6
7
8
9
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ans=0; //任何数与0异或不表,与1异或取反
for(int x:nums)
ans^=x;
return ans;
}
};

两个数组的交集

题目说明

给定两个数组,编写一个函数来计算它们的交集。
输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
我们可以不考虑输出结果的顺序。

题目解析 方法一

先对像个数组进行排序,之后遍历两个数组,找到相同的数则判断这个数再两个数组中出现的次数的最小值,再结果中插入该最小值次数的该数,两个数组都跳过同样的数,继续遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> num;
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
int last=0;
for(int i=0;i<=nums1.size()-1;i++)
{
for(int j=last;j<=nums2.size()-1;j++)
{
if(nums1[i]==nums2[j])
{
//计算个数
int n1=0;
int n2=0;
for(int x=i;x<=nums1.size()-1;x++)
{
if(nums1[x]==nums1[i])
n1++;
}
for(int x=j;x<=nums2.size()-1;x++)
{
if(nums2[x]==nums2[j])
n2++;
}

if(n1>n2)
n1=n2;
else n2=n1;
for(int x=1;x<=n1;x++)
num.push_back(nums1[i]);

i+=n1-1;
last=j+n2;
break;
}
}
}
return num;
}
};

这样实现有些繁琐,逻辑上也有点幼稚,其实想一想就是双指针。
从头遍历两个数组直至结尾,若相等则两个指针都向后移动一位,否则移动对应值较小的哪一个指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> num;
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
int i=0;
int j=0;
while(i!=nums1.size()&&j!=nums2.size())
{
if(nums1[i]==nums2[j])
{
num.push_back(nums1[i]);
i++;
j++;
}
else
{
if(nums1[i]<nums2[j])
{
i++;
}
else
{
j++;
}
}
}
return num;
}
};
题目解析 方法二

使用hash_map记录数据与其出现的次数,先将nums1数组中的数据全部存入到hashmap中,遍历nums2,并查找hashmap中的元素,若没有则舍去,若有,则看其value是否为零,不为零的话则value-1,并且将这个数加入结果数组中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution
{
public:
vector<int> intersect(vector<int> &nums1, vector<int> &nums2)
{
vector<int> num;
if (nums1.empty() || nums2.empty())
return num;
unordered_map<int, int> map;
unordered_map<int, int>::iterator x;
if (nums1.size() < nums2.size())
nums1.swap(nums2);
vector<int>::iterator it;
it = nums1.begin();
for (it = nums1.begin(); it != nums1.end(); it++)
{
int count = 0;
x = map.find(*it);
//找到了
if (x != map.end())
count = x->second;
count++;
map.erase(*it);
map.insert({*it, count});
}
for (it = nums2.begin(); it != nums2.end(); it++)
{
int count = 0;
x = map.find(*it);
if (x != map.end())
{
if (x->second != 0)
{
x->second--;
num.push_back(*it);
}
}
}
return num;
}
};

大数加一

题目说明

给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

题目解析

从后往前遍历判断是否进位即可,就是加法器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution
{
public:
vector<int> plusOne(vector<int> &digits)
{
vector<int>::iterator it;
int carry = 1;
for (it = digits.end() - 1; it >= digits.begin(); it--)
{
*it += carry;
carry = 0;
if (*it == 10)
{
*it = 0;
carry = 1;
continue;
}
break;
}
if (carry == 1)
{
for (it = digits.end() - 1; it >= digits.begin() + 1; it--)
{
*it = *(it - 1);
}
*it = 1;
digits.push_back(0);
}
return digits;
}
};

移动零

题目说明

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

题目解析

使用双指针i,j
i永远指向第一个零,j指向i后面第一个非零。交换ij对应元素的值,直到j超出数组范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution
{
public:
void moveZeroes(vector<int> &nums)
{
int i = 0;
int j = 0;
while (i <= nums.size() - 1)
{
if (nums[i] != 0)
i++;
else
break;
}
if (i == nums.size())
return;
//指向第一个等零的元素
j = i + 1;
while (j < nums.size())
{
while (j <= nums.size() - 1)
{
if (nums[j] == 0)
j++;
else
break;
}
if (j == nums.size())
return;
nums[i++] = nums[j];
nums[j] = 0;
}
}
};

这样代码略显臃肿,可以简化为:
ij从0开始遍历直到j超限。若j对应元素不等零则则交换ij对应元素的值,i++;j++;
若等零,j++;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution
{
public:
void moveZeroes(vector<int> &nums)
{
int i = 0;
int j = 0;
while (j < nums.size())
{
if (nums[j] != 0)
{
//交换位置,但是这里可能都不为零。
swap(nums[i], nums[j]);
i++;
}
j++;
}
}
};

两数之和

题目说明

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

题目解析

按照<index,nums[index]>放进一个unordered_multimap中。遍历数组,对于每一个元素,遍历unordered_multimap,查找匹配元素。因为数组中可能存在两个相同的数恰好是解的情况所以要使用unordered_multimap,并且额外判断有两个相等的值的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution
{
public:
vector<int> twoSum(vector<int> &nums, int target)
{
int fir = 0;
int sec = 0;
//将数组插入一个图里。
vector<int> ans;
unordered_multimap<int, int> map;
unordered_multimap<int, int>::iterator it;
pair<unordered_multimap<int, int>::iterator, unordered_multimap<int, int>::iterator> pair;
//放进去之前先查表,看是否由匹配的,这样不仅节省时间还避免出现nums=[3,3],target=6 这种情况造成多值无法检索。
for (int i = 0; i <= nums.size() - 1; i++)
{
map.insert({nums[i], i});
}
//有两个相等的值
if (map.count(target / 2) == 2)
{
pair = map.equal_range(target / 2);
for (it = pair.first; it != pair.second; it++)
{
ans.push_back(it->second);
}
return ans;
}
//没有两个相等的值
for (int i = 0; i <= nums.size() - 1; i++)
{
it = map.find(target - nums[i]);
if (it != map.end() && it->second != i)
{
ans.push_back(it->second);
ans.push_back(i);
return ans;
}
}
return ans;
}
};

但是这样很复杂,能不能不用判断是否存在两个相等的数的情况恰好是解的情况呢?而且全部插入之后再对数组进行遍历确实很冗余,所以可以一边插入一边寻找答案,这样即使是两数相等的情况也可以解决了,而且不需要使用unordered_multimap,使用unordered_map即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution
{
public:
vector<int> twoSum(vector<int> &nums, int target)
{
//将数组插入一个图里。
vector<int> ans;
unordered_multimap<int, int> map;
unordered_multimap<int, int>::iterator it;
//放进去之前先查表,看是否由匹配的,这样不仅节省时间还避免出现nums=[3,3],target=6 这种情况造成多值无法检索。
for (int i = 0; i <= nums.size() - 1; i++)
{
it = map.find(target - nums[i]);
if (it != map.end()) //找到解了
{
ans.push_back(it->second);
ans.push_back(i);
return ans;
}
else
map.insert({nums[i], i});
}
return ans;
}
};

这样就简单多了。

有效的数独

题目描述

请你判断一个 9x9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 ‘.’ 表示

题目解析

使用unordered_map进行映射即可,映射之前判断是否满足条件即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution
{
public:
bool isValidSudoku(vector<vector<char>> &board)
{
unordered_set<char> set;
unordered_set<char>::iterator it;
//遍历每一行
for (int i = 0; i <= 8; i++)
{
set.clear();
for (int j = 0; j <= 8; j++)
{
if (set.find(board[i][j]) == set.end() || board[i][j] == '.')
set.insert(board[i][j]);
else
return false;
}
}
//遍历每一列
for (int i = 0; i <= 8; i++)
{
set.clear();
for (int j = 0; j <= 8; j++)
{
if (set.find(board[j][i]) == set.end() || board[j][i] == '.')
set.insert(board[j][i]);
else
return false;
}
}
//遍历每一个小正方形
for (int m = 0; m <= 2; m++)
{
for (int n = 0; n <= 2; n++)
{
set.clear();
for (int i = 3 * m; i <= 3 * m + 2; i++)
{
for (int j = 3 * n; j <= 3 * n + 2; j++)
{
if (set.find(board[i][j]) == set.end() || board[i][j] == '.')
set.insert(board[i][j]);
else
return false;
}
}
}
}
return true;
}
};

旋转图像

题目描述

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

题目解析 方法一

元素 i,j 顺时针旋转90°后变为 j,n-i-1
将矩阵分为 n/2圈 即可。
每一圈(第q圈,圈数从0开始)遍历圈内第一行的n-2q-1个元素,元素起始为 q,q

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution
{
public:
void rotate(vector<vector<int>> &matrix)
{
/*
元素 i,j 顺时针旋转90°后变为 j,n-i-1
将矩阵分为 n/2圈 即可。
每一圈(第q圈,圈数从0开始)遍历圈内第一行的n-2q-1个元素,元素起始为 q,q
*/

int n = matrix.size();
for (int q = 0; q <= n / 2 - 1; q++)
{
for (int time = 0; time <= n - 2 * q - 2; time++)
{
int x = q;
int y = q + time;
int temp = matrix[x][y];
int next_x = y;
int next_y = n - x - 1;
//对每一个起始的元素进行四次循环
do
{
//cout<<"x: "<<x<<" y "<<y<<" "<<matrix[x][y]<<endl;
//cout << "next_x: " << next_x << " next_y " << next_y << " " << matrix[next_x][next_y] << endl;
swap(matrix[next_x][next_y], temp);
x = next_x;
y = next_y;
next_x = y;
next_y = n - x - 1;
} while (x != q || y != q + time);
}
}
}
};

题目解析 方法二

元素 i,j 顺时针旋转90°后变为 j,n-i-1
由于:

  1. matrix[i][j] 水平轴反转 –> matrix[n-i-1][j]
  2. matrix[n-i-1][j] 主对角线反转 –> matrix[j][n-i-1]

正好得到结果,所以两次反转即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution
{
public:
void rotate(vector<vector<int>> &matrix)
{
int n = matrix.size();
//水平轴反转
for (int i = 0; i <= n / 2 - 1; i++)
{
matrix[i].swap(matrix[n - 1 - i]);
}
//对角线反转
for (int i = 1; i <= n - 1; i++)
for (int j = 0; j < i; j++)
swap(matrix[i][j], matrix[j][i]);
}
};