总结:数组问题在初级算法层次我认为要关注两种基本方法:数组排序和双指针,有些问题在对数组排序之后会让问题变得简单,例如题目4,题目9。双指针的应用例如题目8,题目9,题目11。而题目9:两数之和则是排序和双指针的综合应用。
题目1:从排序数组中删除重复项
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
思路:每个元素和前一个元素相比较,若相同,则移除,若不同,则继续向后遍历。
int removeDuplicates(vector<int>& nums)
{
for(int i=0;i<nums.size();i++)
{
if(i>0&&nums[i]==nums[i-1])
{
nums.erase(nums.begin()+i);
//每移除一个元素,后面所有元素会前移,下一个元素会移动到当前删除元素位置
i--;
}
}
return nums.size();
}
题目2:买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
思路:将题目转化模型,本质就是就是算价格上涨期间的利润总和。使用贪心算法,只关心当前利润,只要后一天比前一天的价值高即可进行交易,累加这些利润即可。
int maxProfit(vector<int>& prices)
{
int max = 0;
for(int i=1;i<prices.size();i++)
{
int profit = prices[i]-prices[i-1];
if(profit>0)
max += profit;
}
return max;
}
PS:如果看我的思路不太明白为什么这么做,可以看看https://www.jianshu.com/p/d0dbab55b057里面有张图很好解释了为什么这么做。
题目3:旋转数组
给定一个数组,将数组中的元素向右移动 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]
示例 2:
输入: [-1,-100,3,99] 和 k = 2
输出: [3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]
思路:最初思路是算出真实需要旋转的次数times,之后将从后向前数times个数依次移到数组开头,但是这样做显示超时。之后换了一种相反的思路,将前length-times个数依次移动到数组后面,这样就是一个AC的方法。
/*
超时:
void rotate(vector<int>& nums,int k)
{
if(nums.size()==0)
return;
int length = nums.size();
//实际上需要旋转的次数
int times = k%length;
int index = length-1;
for(int i=0;i<times;i++)
{
int num = nums[index];
nums.pop_back();
nums.insert(nums.begin(),num);
}
}
*/
void rotate(vector<int>& nums,int k)
{
if(nums.size()==0)
return;
int length = nums.size();
//实际上需要旋转的次数,length次旋转等于没有旋转
int times = k%length;
for(int i=0;i<length-times;i++)
{
nums.push_back(nums[0]);
nums.erase(nums.begin());
}
}
题目4:存在重复
给定一个整数数组,判断是否存在重复元素。
如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。
示例 1:
输入: [1,2,3,1]
输出: true
示例 2:
输入: [1,2,3,4]
输出: false
示例 3:
输入: [1,1,1,3,3,4,3,2,4,2]
输出: true
思路:先排序,之后依次将元素和前一个元素比较,若相同则说明有重复元素存在。
bool containsDuplicate(vector<int>& nums)
{
if(nums.size()<=1)
return false;
sort(nums.begin(),nums.end());
for(int i=1;i<nums.size();i++)
if(nums[i]==nums[i-1])
return true;
return false;
}
题目5:只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
思路:利用异或去做,相同数字异或为0,数字与0异或为原数字,将所有数字异或一遍剩下的就是单独那个数字。
int singleNumber(vector<int>& nums)
{
int result = 0;
for(int i=0;i<nums.size();i++)
result = result^nums[i];
return result;
}
题目6:两个数组的交集 II
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
我们可以不考虑输出结果的顺序。
思路:遍历短数组每个元素,并且在较长数组中寻找相对应元素,找到则添加到结果数组中,并且删除长数组中的对应元素,因为题目要求输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致,不删除可能会导致长数组中一个元素对应短数组中多个元素的情况。
vector<int> intersect(vector<int>& nums1, vector<int>& nums2)
{
vector<int> result;
if(nums1.size()>nums2.size())
swap(nums1,nums2);
for(int i=0;i<nums1.size();i++)
{
for(int j=0;j<nums2.size();j++)
{
if(nums1[i]==nums2[j])
{
result.push_back(nums1[i]);
nums2.erase(nums2.begin()+j);
break;
}
}
}
return result;
}
题目7:加一
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。
示例 2:
输入: [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321。
思路:从低位(数组最后一个元素)向高位做加法运算。
vector<int> plusOne(vector<int>& digits)
{
//进位数
int temp = 1;
for(int i=digits.size()-1;i>=0;i--)
{
int num = digits[i]+temp;
digits[i] = num%10;
temp = num/10;
if(temp==0)
break;
}
//要注意最高位的进位
if(temp!=0)
digits.insert(digits.begin(),temp);
return digits;
}
题目8:移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
思路:point1指针从数组开头向后寻找为0的数,之后由point2寻找point1之后的第一个非0数,二者交换。当point1超出数组界限(数组中没有0)或者point1后全为0,则交换过程结束。
void moveZeroes(vector<int>& nums)
{
if(nums.size()==0)
return;
int point1 = 0;
int point2 = 0;
bool flag = true;
while(point1<nums.size())
{
flag = true;
while(point1<nums.size()&&nums[point1]!=0)
point1++;
//找到point1指向的0之后的第一个非0数
for(int i=point1+1;i<nums.size();i++)
{
if(nums[i]!=0)
{
point2 = i;
flag = false;
break;
}
}
//如果point1后边全是0了,那么交换过程已经完成
if(flag)
break;
if(point1<nums.size())
{
int temp = nums[point1];
nums[point1] = nums[point2];
nums[point2] = temp;
}
}
}
题目9:两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
思路:先排序数组,并且在数组左右两边各放一个指针,不停将指针指向数字相加,若大于目标则将右指针向前推一位(因为排过序所以等于缩小二者的和),反之则将左指针向后推一位(增大二者的和)。还有一点很重要的是要记录排序前数组的下标,排序后数组会发生改变。
vector<int> twoSum(vector<int>& nums,int target)
{
vector<int> result;
vector<int> index;
for(int i=0;i<nums.size();i++)
index.push_back(i);
//冒泡排序,并调整对应下标
for (int i=0;i<nums.size()-1;i++)
{
for (int j=0;j<nums.size()-1-i;j++)
{
if (nums[j]>nums[j+1])
{
int temp1 = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp1;
//对下标也相应调整
int temp2 = index[j];
index[j] = index[j+1];
index[j+1] = temp2;
}
}
}
int point1 = 0;
int point2 = nums.size()-1;
while(point1<point2)
{
if(nums[point1]+nums[point2]==target)
{
result.push_back(index[point1]);
result.push_back(index[point2]);
break;
}
if(nums[point1]+nums[point2]>target)
point2--;
if(nums[point1]+nums[point2]<target)
point1++;
}
return result;
}
题目10:有效的数独
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
1.数字 1-9 在每一行只能出现一次。
2.数字 1-9 在每一列只能出现一次。
3.数字 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
示例 2:
输入:
[
["8","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"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
//判断行
bool judgeRow(vector<vector<char> >& board,int x,int y)
{
for(int i=0;i<9;i++)
{
if(i!=y&&board[x][y]==board[x][i])
return false;
}
return true;
}
//判断列
bool judgeCol(vector<vector<char> >& board,int x,int y)
{
for(int i=0;i<9;i++)
{
if(i!=x&&board[x][y]==board[i][y])
return false;
}
return true;
}
//判断九宫格
bool judgeBox(vector<vector<char> >& board,int x,int y)
{
int row = (x/3)*3;
int col = (y/3)*3;
for(int i=row;i<row+3;i++)
{
for(int j=col;j<col+3;j++)
{
if(i!=x&&j!=y&&board[x][y]==board[i][j])
return false;
}
}
return true;
}
bool isValidSudoku(vector<vector<char> >& board)
{
for(int i=0;i<board.size();i++)
{
for(int j=0;j<board[i].size();j++)
{
if(board[i][j]=='.')
continue;
if(!judgeRow(board,i,j)||!judgeCol(board,i,j)||!judgeBox(board,i,j))
return false;
}
}
return true;
}
题目11:旋转图像
给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
思路:顺时针旋转分两步:1.将第一行与最后一行交换,倒数第二行与第二行交换,以此类推;2.矩阵转置(行变列,列变行)。
void rotate(vector<vector<int> >& matrix)
{
int point1 = 0;
int point2 = matrix.size()-1;
while(point1<point2)
{
vector<int> temp = matrix[point1];
matrix[point1] = matrix[point2];
matrix[point2] = temp;
point1++;
point2--;
}
//转置
for(int i=0;i<matrix.size();i++)
{
for(int j=0;j<matrix[0].size();j++)
{
if(j<=i)
continue;
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}